2010-11-17 6 views
5

Ich habe Probleme beim Konvertieren einer Java/JSON-Map in ein verwendbares F # -Objekt.Decodierung einer Java/JSON-Map in ein F # -Objekt

Hier ist das Herz meines Code:

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 
    () 

Die JSON, die das sieht aus wie mein RPC-Methode zurückgegeben wird:

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

Ich versuche es zu einem F # Datacontract zu kartieren, die aussieht wie folgt aus:

[<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 
} 

Schließlich wird die F # Methode, dass die tatsächliche RPC-Aufruf (Rpc.getJa führt vaJSONMap oben) sieht so aus:

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 
     ) 

Zur Kompilierzeit bekomme ich keine Fehler. Meine RPC-Methode wird aufgerufen, und ein Ergebnis wird zurückgegeben (mithilfe von Fiddler, um den tatsächlichen Aufruf & zurückzukehren). Es scheint jedoch, dass die F # Probleme hat, den JSON mit meinem DataContract abzugleichen, da returnedMap ganz oben immer Null ist.

Alle Gedanken oder Ratschläge würden sehr geschätzt werden. Vielen Dank.

Antwort

1

Hmm das ist ein kompliziertes Problem. Ich gehe davon aus:

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

könnte eine variable Anzahl von Feldern enthalten. Und in JSON übersetzt sich die Notation in ein Objekt (JavaScript-Objekte sind im Grunde (oder sehr ähnlich) zu Maps). Ich weiß nicht, ob das direkt in F # übersetzt wird.

Es kann verhindert werden, dass F # statische Eingabe im Gegensatz zur dynamischen Typisierung von Javascript nicht erlaubt wird.

müssen Sie möglicherweise die Konvertierungsroutine selbst schreiben.


Ok ein paar kleine Fehler in den Datenverträge gibt es lässt die JsonMap neu definieren und entfernen Sie das „Javaclass“ -Attribut, da es nicht in th JSON Probe vorgesehen ist (es ist eine höhere Ebene nach oben), und es sieht aus, als ob die keyvaulepair mir nicht ist Serialisierung, lässt so unsere eigene Art definieren:

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

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

und erstellen sie eine deserialize Funktion:

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 
    () 

ich in der Lage bin str deserialisieren in die entsprechende Objektstruktur einfügen. Zwei Dinge, die ich gerne beantwortet sehen möchte, sind

1) Warum zwingt mich .NET, @ Zeichen nach den Feldnamen anzufügen? 2) Was ist der beste Weg, um die Konvertierung durchzuführen? Ich würde vermuten, dass ein abstrakter Syntaxbaum, der die JSON-Struktur repräsentiert, der Weg sein könnte, und dann diesen in die neue Zeichenkette zu parsen. Ich bin mit AST und ihrem Parsing allerdings nicht besonders vertraut.

Vielleicht könnte einer der F # -Experten helfen oder ein besseres Übersetzungsschema erstellen?


schließlich Zugabe zurück in dem Ergebnistyp:

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

und bekehrt Map-Funktion (Arbeiten in diesem Fall - aber hat viele Bereiche der Schwäche einschließlich rekursive Karten Definitionen etc.):

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 

Es immer noch inisits auf dem @ Zeichen an verschiedenen Stellen - die ich nicht bekomme ...


Hinzufügen konvertieren w/Abhilfe für das Ampersand Ausgabe

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 

Zugabe:

[<DataContract>] 

über dem Rekord behebt das Problem Ampersand.

+0

Ich bemerkte, dass, wenn ich F # Satzarten in RavenDB gespeichert, würden sie mit dem regulären Eigenschaftsnamen gespeichert werden und die gleichen Eigenschaftsnamen durch ein @ -Zeichen gefolgt. Ich vermute, dass die @ Version das Hintergrundfeld für die öffentliche Eigenschaft im Datensatztyp ist. Das Erstellen meiner eigenen Klasse anstelle der Verwendung eines Datensatzes hat die zusätzlichen @ -Eigenschaften beseitigt - Sie müssen möglicherweise so etwas tun. –

2

Hier ist, was ich gekocht:

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)]