将Java/JSON映射解码为F#对象

Mik*_*icz 5 java f# json map datacontract

我将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
    ()
Run Code Online (Sandbox Code Playgroud)

我的RPC方法返回的JSON如下所示:

{"id":1, "result":
    {"map":
        {"Momentum":12, "Corporate":3, "Catalyst":1},
     "javaClass":"java.util.HashMap"}
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试将其映射到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
}
Run Code Online (Sandbox Code Playgroud)

最后,执行实际RPC调用的F#方法(上面的Rpc.getJavaJSONMap)如下所示:

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 
        )
Run Code Online (Sandbox Code Playgroud)

在编译时我没有错误.调用我的RPC方法,并返回一个结果(使用Fiddler查看实际的调用和返回).但是,看起来F#在将JSON匹配到我的DataContract时遇到了麻烦,因为最顶层的returnedMap始终为null.

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

aka*_*nom 1

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

{"map": 
        {"Momentum":12, "Corporate":3, "Catalyst":1}, 
     "javaClass":"java.util.HashMap"} 
Run Code Online (Sandbox Code Playgroud)

可以包含可变数量的字段。JSON 表示法转换为对象(javascript 对象基本上(或非常类似于)地图)。我不知道这是否会直接转换为 F#。

F# 静态类型与 javascript 的动态类型可能会阻止这种情况。

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


好的,数据契约中存在一些小错误,让我们重新定义 JsonMap 并删除“javaclass”属性,因为它不在提供的 JSON 示例中(它是更高级别的),并且看起来好像 keyvaulepair me 没有序列化,所以让我们定义我们自己的类型:

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

type JSONMap<'T, 'S> = { 
    [<DataMember>] 
    mutable map : JsonKeyValuePair<'T, 'S> array 
} 
Run Code Online (Sandbox Code Playgroud)

并创建一个反序列化函数:

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 = "{\"map@\":[{\"key@\":\"a\",\"value@\":1},{\"key@\":\"b\",\"value@\":2}]}"
    let o  = deserializeString<JSONMap<string, int>> json
    ()
Run Code Online (Sandbox Code Playgroud)

我能够将字符串反序列化为适当的对象结构。我希望看到答案的两件事是

1) 为什么 .NET 强迫我在字段名称后面附加 @ 字符?2)进行转换的最佳方法是什么?我猜测表示 JSON 结构的抽象语法树可能是可行的方法,然后将其解析为新字符串。不过我对 AST 及其解析不太熟悉。

也许 F# 专家之一能够提供帮助或提出更好的翻译方案?


最后添加回结果类型:

[<DataContract>] 
type Result<'T> = { 
    [<DataMember>] 
    mutable javaClass: string 
    [<DataMember>] 
    mutable result: 'T 
} 
Run Code Online (Sandbox Code Playgroud)

和转换映射函数(在这种情况下有效 - 但有许多弱点,包括递归映射定义等):

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
Run Code Online (Sandbox Code Playgroud)

它仍然在不同地方的 @ 符号上出现 - 我不明白......


添加转换并解决与号问题

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 = "{\"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, "\"map@\":[" + 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
Run Code Online (Sandbox Code Playgroud)

添加:

[<DataContract>] 
Run Code Online (Sandbox Code Playgroud)

记录上方修复了 & 符号问题。