2010-11-17 3 views
5

Je n'arrive pas à convertir une carte Java/JSON en un objet F # utilisable.Décodage d'une mappe Java/JSON dans un objet F #

est ici au cœur de mon 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 
    () 

Le JSON qui est retourné par ma méthode RPC ressemble à ceci:

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

je tente de la carte à un F # DataContract qui semble comme ceci:

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

Enfin, la méthode F # qui effectue l'appel RPC réel (Rpc.getJa vaJSONMap ci-dessus) ressemble à ceci:

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 
     ) 

Au moment de la compilation, je n'ai aucune erreur. Ma méthode RPC est appelée, et un résultat est renvoyé (en utilisant Fiddler pour voir l'appel réel & return). Cependant, il semble que le F # ait du mal à faire correspondre le JSON dans mon DataContract, puisque le ReturnMap tout en haut est toujours nul.

Des idées ou des conseils seraient grandement appréciés. Je vous remercie.

Répondre

1

Hmm c'est un problème compliqué. Je suppose que:

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

peut contenir une quantité variable de champs. Et en notation JSON se traduit par un objet (les objets javascript sont fondamentalement (ou très similaires) aux cartes). Je ne sais pas si cela se traduira par F # directement.

Il peut être évité de ne pas être autorisé par le typage statique F # par rapport au typage dynamique de javascript.

vous devrez peut-être écrire la routine de conversion vous-même.


Ok il y a quelques petits bugs dans les contrats de données permet de redéfinir la JsonMap et de supprimer l'attribut « JavaClass » comme il est pas ième échantillon de JSON fourni (il est un niveau plus haut), et il semble que le keyvaulepair me ne sérialisation, donc permet de définir notre propre type:

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

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

et créer une fonction deserialize:

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

Je suis en mesure de désérialiser une str dans la structure d'objet appropriée. Deux choses que j'aimerais voir répondre sont

1) Pourquoi est-ce que .NET m'oblige à ajouter @ caractères après les noms de champs? 2) Quelle est la meilleure façon de faire la conversion? Je suppose qu'un arbre de syntaxe abstraite représentant la structure JSON pourrait être le chemin à parcourir, et ensuite analyser cela dans la nouvelle chaîne. Je ne suis pas très familier avec AST et leur analyse. Peut-être que l'un des experts F # pourrait être en mesure d'aider ou de proposer un meilleur schéma de traduction?


enfin réintégrant dans le type de résultat:

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

et une fonction de carte de conversion (travaux dans ce cas - mais a beaucoup de points faibles, y compris les définitions de carte récursives, 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 

Il est toujours indiqué sur le signe @ à divers endroits - que je ne reçois pas ...


convertir avec ajout/solution pour la question esperluette

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 

ajouter:

[<DataContract>] 

au-dessus du dossier résout le problème Ampersand.

+0

Je remarqué que lorsque j'emmagasinés F # types d'enregistrement dans RavenDB, ils seraient stockés avec le nom de la propriété régulière, et le même nom de propriété suivi d'un signe @. Je soupçonne que la version @ est le champ de sauvegarde pour la propriété publique dans le type d'enregistrement. Créer ma propre classe au lieu d'utiliser un enregistrement s'est débarrassé des propriétés extra @ - vous devrez peut-être faire quelque chose comme ça. –

2

Voici ce que je cuisinais jusqu'à:

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