2010-11-17 14 views
5

Tengo problemas para convertir un mapa Java/JSON en un objeto utilizable F #.Decodificación de un mapa Java/JSON en un objeto F #

Aquí está el corazón de mi código:

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

El JSON que se devuelve por mi método RPC se parece a esto:

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

Estoy intentando hacer un mapa a un F # DataContract que parece de esta manera:

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

Por último, el método # F que realiza la llamada RPC real (Rpc.getJa vaJSONMap arriba) se ve así:

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 
     ) 

En tiempo de compilación me sale ningún error. Se llama a mi método RPC y se devuelve un resultado (usando Fiddler para ver la llamada real & return). Sin embargo, parece que el F # tiene problemas para hacer coincidir el JSON en mi DataContract, ya que returnedMap en la parte superior siempre es nulo.

Cualquier pensamiento o consejo sería muy apreciado. Gracias.

Respuesta

1

Hmm este es un problema complicado. Supongo que esto:

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

podría contener una cantidad variable de campos. Y en la notación JSON se traduce a un objeto (los objetos javascript son básicamente (o muy similares) a los mapas). No sé si esto se traducirá directamente en F #.

Se puede evitar que no se permita mediante el tipado estático de F # en comparación con el tipado dinámico de javascript.

puede que tenga que escribir la rutina de conversión usted mismo.


OK Hay un par de pequeños errores en los contratos de datos permite redefinir el JsonMap y retire el "JavaClass" atributo, ya que no está en la muestra JSON ° proporcionado (que es un nivel más arriba), y parece que la keyvaulepair a mí, no es serializando, por lo que permite definir nuestro propio tipo:

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

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

y crear una función deserializar:

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

soy capaz de deserializar un str ing dentro de la estructura de objeto apropiada. Dos cosas que me gustaría ver respondidas son

1) ¿por qué .NET me obliga a agregar @ caracteres después de los nombres de los campos? 2) ¿Cuál es la mejor manera de hacer la conversión? Supongo que un árbol sintáctico abstracto que representa la estructura JSON podría ser el camino a seguir y luego analizarlo en la nueva cadena. No estoy muy familiarizado con AST y su análisis sin embargo.

¿Quizás alguno de los expertos de F # podría ayudar o proponer un mejor esquema de traducción?


por último la adición de nuevo en el tipo de resultado:

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

y una función de mapa de conversión (funciona en este caso - pero tiene muchas áreas de debilidad que incluyen las definiciones mapa recursivas 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 

Todavía aparece en el signo @ en varios lugares, que no entiendo ...


añadiendo convertir w/solución para el problema de signo

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 

añadiendo:

[<DataContract>] 

por encima del récord corrige el problema Ampersand.

+0

he notado que cuando Almacené tipos F # registro en RavenDB, serían almacenados con el nombre de la propiedad regular, y el mismo nombre de la propiedad seguido por un signo @. Sospecho que la versión @ es el campo de respaldo para la propiedad pública en el tipo de registro. Al crear mi propia clase en lugar de utilizar un registro, eliminé las propiedades @ adicionales, es posible que tenga que hacer algo como eso. –

2

Esto es lo que cociné:

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