Декодирование карты Java / JSON в объект F # - PullRequest
5 голосов
/ 17 ноября 2010

У меня проблемы с преобразованием карты 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
    ()

JSON, возвращаемый моим методом RPCвыглядит так:

{"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
}

Наконец, метод F #, который выполняет фактический вызов RPC (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 
        )

Во время компиляции я не получаю никаких ошибок.Мой метод RPC вызывается, и результат возвращается (используя Fiddler, чтобы увидеть фактический вызов и возврат).Однако, похоже, что F # испытывает проблемы с сопоставлением JSON с моим DataContract, так как returnMap в самом верху всегда нулевой.

Любые мысли или советы будут высоко оценены.Спасибо.

Ответы [ 2 ]

2 голосов
/ 17 ноября 2010

Вот что я приготовил:

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)]
1 голос
/ 17 ноября 2010

Хм, это сложная проблема.Я предполагаю это:

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

может содержать переменное количество полей.А в JSON нотация переводится в объект (объекты JavaScript в основном (или очень похожи) на карты).Я не знаю, переведет ли это непосредственно на F #.

Это может быть запрещено статической типизацией F # по сравнению с динамической типизацией javascript.

вам, возможно, придется написать процедуру преобразования самостоятельно.


Ok тампара небольших ошибок в контрактах данных позволяет переопределить JsonMap и удалить атрибут «javaclass», поскольку его нет в предоставленном примере JSON (это более высокий уровень), и похоже, что пара ключей для меняне сериализуем, поэтому давайте определим наш собственный тип:

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

Я могу десериализовать строку в соответствующую структуру объекта.Я хотел бы получить ответ на две вещи:

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 = "{\"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

добавление:

[<DataContract>] 

над записью исправляет проблему амперсанда.

...