2010-11-17 29 views
5

我无法将Java/JSON映射转换为可用的F#对象。将Java/JSON映射解码为F#对象

这里是我的代码的心脏:

member this.getMapFromRpcAsynchronously = 
    Rpc.getJavaJSONMap (new Action<_>(this.fillObjectWithJSONMap)) 
    () 

member this.fillObjectWithJSONMap (returnedMap : JSONMap<string, int> option) = 
    let container = Option.get(returnedMap) 
    let map = container.map 
    for thing in map do 
     this.myObject.add thing.key 
     // do stuff with thing 
    () 

多数民众赞成我的RPC方法返回的JSON看起来是这样的:

{"id":1, "result": 
    {"map": 
     {"Momentum":12, "Corporate":3, "Catalyst":1}, 
    "javaClass":"java.util.HashMap"} 
} 

我试图将其映射到F#DataContract看起来像这样:

[<DataContract>] 
type JSONMap<'T, 'S> = { 
    [<DataMember>] 
    mutable map : KeyValuePair<'T, 'S> array 
    [<DataMember>] 
    mutable javaClass : string 
} 

[<DataContract>] 
type JSONSingleResult<'T> = { 
    [<DataMember>] 
    mutable javaClass: string 
    [<DataMember>] 
    mutable result: 'T 
} 

最后,执行实际RPC调用的F#方法(Rpc.getJa上面的vaJSONMap)看起来像这样:

let getJavaJSONMap (callbackUI : Action<_>) = 
    ClientRpc.getSingleRPCResult<JSONSingleResult<JSONMap<string, int>>, JSONMap<string, int>> 
     "MyJavaRpcClass" 
     "myJavaRpcMethod" 
     "" // takes no parameters 
     callbackUI 
     (fun (x : option<JSONSingleResult<JSONMap<string, int>>>) -> 
      match x.IsSome with 
       | true -> Some(Option.get(x).result) 
       | false -> None 
     ) 

在编译时,我没有得到任何错误。我的RPC方法被调用,并返回一个结果(使用Fiddler来查看实际调用&返回)。然而,看起来F#在将JSON匹配到我的DataContract时遇到了问题,因为最上面的返回的映射始终为空。

任何想法或建议将不胜感激。谢谢。

回答

1

嗯,这是一个复杂的问题。我假设:

{"map": 
     {"Momentum":12, "Corporate":3, "Catalyst":1}, 
    "javaClass":"java.util.HashMap"} 

可能包含可变数量的字段。而在JSON中,符号转换为对象(javascript对象基本上(或者非常类似于地图))。我不知道这是否会直接转换为F#。

它可能会阻止F#静态类型与JavaScript的动态类型的不匹配。

您可能必须自己编写转换例程。


确定有一对情侣在所述数据契约小错误的允许重新定义JsonMap并除去“javaclass”属性,因为它是没有设置第JSON样品(它是一个较高的水平上),并它看起来好像keyvaulepair我没有序列化,所以让我们自己定义的类型:

type JsonKeyValuePair<'T, 'S> = { 
    [<DataMember>] 
    mutable key : 'T 
    [<DataMember>] 
    mutable value : 'S 
} 

type JSONMap<'T, 'S> = { 
    [<DataMember>] 
    mutable map : JsonKeyValuePair<'T, 'S> array 
} 

,并创建一个反序列化功能:

let internal deserializeString<'T> (json: string) : 'T = 
    let deserializer (stream : MemoryStream) = 
     let jsonSerializer 
      = Json.DataContractJsonSerializer(
       typeof<'T>) 
     let result = jsonSerializer.ReadObject(stream) 
     result 


    let convertStringToMemoryStream (dec : string) : MemoryStream = 
     let data = Encoding.Unicode.GetBytes(dec); 
     let stream = new MemoryStream() 
     stream.Write(data, 0, data.Length); 
     stream.Position <- 0L 
     stream 

    let responseObj = 
     json 
      |> convertStringToMemoryStream 
      |> deserializer 

    responseObj :?> 'T 


let run2() = 
    let json = "{\"[email protected]\":[{\"[email protected]\":\"a\",\"[email protected]\":1},{\"[email protected]\":\"b\",\"[email protected]\":2}]}" 
    let o = deserializeString<JSONMap<string, int>> json 
    () 

我能够反序列化STR进入适当的对象结构。我想看到的两件事情是:

1)为什么.NET迫使我在字段名称后追加@字符? 2)什么是转换的最佳方式?我猜想代表JSON结构的抽象语法树可能是要走的路,然后将其解析为新的字符串。尽管我不太熟悉AST和他们的解析。

也许其中一位F#专家可能能够帮助或提出更好的翻译方案?


的结果类型最后加回:

[<DataContract>] 
type Result<'T> = { 
    [<DataMember>] 
    mutable javaClass: string 
    [<DataMember>] 
    mutable result: 'T 
} 

和转换地图功能(在这种情况下的作品 - 但有弱点的许多领域,包括递归地图定义等):

let convertMap (json: string) = 
    let mapToken = "\"map\":" 
    let mapTokenStart = json.IndexOf(mapToken) 
    let mapTokenStart = json.IndexOf("{", mapTokenStart) 
    let mapObjectEnd = json.IndexOf("}", mapTokenStart) 
    let mapObjectStart = mapTokenStart 
    let mapJsonOuter = json.Substring(mapObjectStart, mapObjectEnd - mapObjectStart + 1) 
    let mapJsonInner = json.Substring(mapObjectStart + 1, mapObjectEnd - mapObjectStart - 1) 
    let pieces = mapJsonInner.Split(',') 
    let convertPiece state (piece: string) = 
     let keyValue = piece.Split(':') 
     let key = keyValue.[0] 
     let value = keyValue.[1] 
     let newPiece = "{\"key\":" + key + ",\"value\":" + value + "}" 
     newPiece :: state 

    let newPieces = Array.fold convertPiece [] pieces 
    let newPiecesArr = List.toArray newPieces 
    let newMap = String.Join(",", newPiecesArr) 
    let json = json.Replace(mapJsonOuter, "[" + newMap + "]") 
    json 



let json = "{\"id\":1, \"result\": {\"map\": {\"Momentum\":12, \"Corporate\":3, \"Catalyst\":1}, \"javaClass\":\"java.util.HashMap\"} } " 
printfn <| Printf.TextWriterFormat<unit>(json) 
let json2 = convertMap json 
printfn <| Printf.TextWriterFormat<unit>(json2) 
let obj = deserializeString<Result<JSONMap<string,int>>> json2 

它仍然在各个地方的@标志上徘徊 - 我不明白...


增加转换瓦特/解决方法的符号问题

let convertMapWithAmpersandWorkAround (json: string) = 
    let mapToken = "\"map\":" 
    let mapTokenStart = json.IndexOf(mapToken) 
    let mapObjectEnd = json.IndexOf("}", mapTokenStart) 
    let mapObjectStart = json.IndexOf("{", mapTokenStart) 
    let mapJsonOuter = json.Substring(mapTokenStart , mapObjectEnd - mapTokenStart + 1) 
    let mapJsonInner = json.Substring(mapObjectStart + 1, mapObjectEnd - mapObjectStart - 1) 
    let pieces = mapJsonInner.Split(',') 
    let convertPiece state (piece: string) = 
     let keyValue = piece.Split(':') 
     let key = keyValue.[0] 
     let value = keyValue.[1] 
     let newPiece = "{\"[email protected]\":" + key + ",\"[email protected]\":" + value + "}" 
     newPiece :: state 

    let newPieces = Array.fold convertPiece [] pieces 
    let newPiecesArr = List.toArray newPieces 
    let newMap = String.Join(",", newPiecesArr) 
    let json = json.Replace(mapJsonOuter, "\"[email protected]\":[" + newMap + "]") 
    json 



let json = "{\"id\":1, \"result\": {\"map\": {\"Momentum\":12, \"Corporate\":3, \"Catalyst\":1}, \"javaClass\":\"java.util.HashMap\"} } " 
printfn <| Printf.TextWriterFormat<unit>(json) 
let json2 = convertMapWithAmpersandWorkAround json 
printfn <| Printf.TextWriterFormat<unit>(json2) 
let obj = deserialize<Result<JSONMap<string,int>>> json2 

补充说:

[<DataContract>] 

上面记录修复和号问题。

+0

我注意到,当我保存F#记录类型为RavenDB,它们将被存储与普通的属性名称,后跟一个@符号相同的属性名称。我怀疑@版本是记录类型中公共属性的支持字段。创建我自己的类,而不是使用记录摆脱了额外的@属性 - 你可能不得不这样做。 – 2010-11-17 18:52:49

2

以下是我熟了起来:

open System.Web.Script.Serialization // from System.Web.Extensions assembly 

let s = @" 
    {""id"":1, ""result"": 
     {""map"": 
      {""Momentum"":12, ""Corporate"":3, ""Catalyst"":1}, 
     ""javaClass"":""java.util.HashMap""} 
    } 
    " 

let jss = new JavaScriptSerializer() 
let o = jss.DeserializeObject(s) 

// DeserializeObject returns nested Dictionary<string,obj> objects, typed 
// as 'obj'... so add a helper dynamic-question-mark operator 
open System.Collections.Generic 
let (?) (o:obj) name : 'a = (o :?> Dictionary<string,obj>).[name] :?> 'a 

printfn "id: %d" o?id 
printfn "map: %A" (o?result?map 
        |> Seq.map (fun (KeyValue(k:string,v)) -> k,v) 
        |> Seq.toList) 
// prints: 
// id: 1 
// map: [("Momentum", 12); ("Corporate", 3); ("Catalyst", 1)]