I'm having trouble converting a Java/JSON map into a usable F# object.
我将Java / JSON映射转换为可用的F#对象时遇到问题。
Here's the heart of my 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
()
The JSON that's returned by my RPC method looks like this:
我的RPC方法返回的JSON如下所示:
{"id":1, "result":
{"map":
{"Momentum":12, "Corporate":3, "Catalyst":1},
"javaClass":"java.util.HashMap"}
}
I'm attempting to map it to an F# DataContract which looks like this:
我正在尝试将其映射到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
}
Finally, the F# method that performs the actual RPC call (Rpc.getJavaJSONMap above) looks like this:
最后,执行实际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
)
At compile time I get no errors. My RPC method is called, and a result is returned (using Fiddler to see the actual call & return). However, it appears the F# is having trouble matching the JSON into my DataContract, since returnedMap at the very top is always null.
在编译时我没有错误。调用我的RPC方法,并返回一个结果(使用Fiddler查看实际的调用和返回)。但是,看起来F#在将JSON匹配到我的DataContract时遇到了麻烦,因为最顶层的returnedMap始终为null。
Any thoughts or advice would be greatly appreciated. Thank you.
任何想法或建议将不胜感激。谢谢。
2 个解决方案
#1
2
Here's what I cooked up:
这是我做的东西:
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)]
#2
1
Hmm this is a complicated problem. I assume this:
嗯,这是一个复杂的问题。我假设这个:
{"map":
{"Momentum":12, "Corporate":3, "Catalyst":1},
"javaClass":"java.util.HashMap"}
could contain a variable amount of fields. And in JSON notation translates to an object (javascript objects are basically (or very similar) to maps). I don't know if this will translate to F# directly.
可能包含可变数量的字段。并且在JSON表示法中转换为对象(javascript对象基本上(或非常类似于)映射)。我不知道这是否会直接转换为F#。
It might be prevented not be allowed by F# static typing versus the javascript's dynamic typing.
F#静态类型与javascript的动态类型相比,可能会阻止它。
you may have to write the conversion routine yourself.
您可能必须自己编写转换例程。
Ok there are a couple of small bugs in the the data contracts lets redefine the JsonMap and remove the "javaclass" attribute as it is not in th JSON sample provided (it is a higher level up), and it looks as though the keyvaulepair to me is not serializing, so lets define our own type:
好吧,数据契约中有一些小错误让我们重新定义JsonMap并删除“javaclass”属性,因为它不是在提供的JSON示例中(它是更高级别的),它看起来好像是keyvaulepair我没有序列化,所以让我们定义自己的类型:
type JsonKeyValuePair<'T, 'S> = {
[<DataMember>]
mutable key : 'T
[<DataMember>]
mutable value : 'S
}
type JSONMap<'T, 'S> = {
[<DataMember>]
mutable map : JsonKeyValuePair<'T, 'S> array
}
and create a deserialize function:
并创建一个反序列化函数:
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
()
I am able to deserialize a string into the appropriate object structure. Two things I would like to see answered are
我能够将字符串反序列化为适当的对象结构。我希望看到的两件事是
1) why is .NET forcing me to append @ characters after the field names? 2) what is the best way to do the conversion? I would guess an abstract syntax tree representing the JSON structure might be the way to go, and then to parse that into the new string. I am not super familiar with AST and their parsing though.
1)为什么.NET强迫我在字段名后附加@字符? 2)进行转换的最佳方法是什么?我猜一个表示JSON结构的抽象语法树可能是要走的路,然后将其解析为新的字符串。我不是非常熟悉AST和他们的解析。
Perhaps one of the F# experts might be able to help or come up with a better translation scheme?
或许其中一位F#专家可以提供帮助或提出更好的翻译方案?
lastly adding back in the result type:
最后在结果类型中添加:
[<DataContract>]
type Result<'T> = {
[<DataMember>]
mutable javaClass: string
[<DataMember>]
mutable result: 'T
}
and a convert map function (works in this case - but has many areas of weakness including recursive map definitions 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
It still inisits on the @ sign in various places - which I don't get...
它仍然存在于各个地方的@符号 - 我没有得到...
adding convert w/ workaround for the ampersand issue
为&符问题添加转换w / workaround
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
adding:
[<DataContract>]
above the record fixes the Ampersand issue.
在记录之上修复了Ampersand问题。
#1
2
Here's what I cooked up:
这是我做的东西:
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)]
#2
1
Hmm this is a complicated problem. I assume this:
嗯,这是一个复杂的问题。我假设这个:
{"map":
{"Momentum":12, "Corporate":3, "Catalyst":1},
"javaClass":"java.util.HashMap"}
could contain a variable amount of fields. And in JSON notation translates to an object (javascript objects are basically (or very similar) to maps). I don't know if this will translate to F# directly.
可能包含可变数量的字段。并且在JSON表示法中转换为对象(javascript对象基本上(或非常类似于)映射)。我不知道这是否会直接转换为F#。
It might be prevented not be allowed by F# static typing versus the javascript's dynamic typing.
F#静态类型与javascript的动态类型相比,可能会阻止它。
you may have to write the conversion routine yourself.
您可能必须自己编写转换例程。
Ok there are a couple of small bugs in the the data contracts lets redefine the JsonMap and remove the "javaclass" attribute as it is not in th JSON sample provided (it is a higher level up), and it looks as though the keyvaulepair to me is not serializing, so lets define our own type:
好吧,数据契约中有一些小错误让我们重新定义JsonMap并删除“javaclass”属性,因为它不是在提供的JSON示例中(它是更高级别的),它看起来好像是keyvaulepair我没有序列化,所以让我们定义自己的类型:
type JsonKeyValuePair<'T, 'S> = {
[<DataMember>]
mutable key : 'T
[<DataMember>]
mutable value : 'S
}
type JSONMap<'T, 'S> = {
[<DataMember>]
mutable map : JsonKeyValuePair<'T, 'S> array
}
and create a deserialize function:
并创建一个反序列化函数:
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
()
I am able to deserialize a string into the appropriate object structure. Two things I would like to see answered are
我能够将字符串反序列化为适当的对象结构。我希望看到的两件事是
1) why is .NET forcing me to append @ characters after the field names? 2) what is the best way to do the conversion? I would guess an abstract syntax tree representing the JSON structure might be the way to go, and then to parse that into the new string. I am not super familiar with AST and their parsing though.
1)为什么.NET强迫我在字段名后附加@字符? 2)进行转换的最佳方法是什么?我猜一个表示JSON结构的抽象语法树可能是要走的路,然后将其解析为新的字符串。我不是非常熟悉AST和他们的解析。
Perhaps one of the F# experts might be able to help or come up with a better translation scheme?
或许其中一位F#专家可以提供帮助或提出更好的翻译方案?
lastly adding back in the result type:
最后在结果类型中添加:
[<DataContract>]
type Result<'T> = {
[<DataMember>]
mutable javaClass: string
[<DataMember>]
mutable result: 'T
}
and a convert map function (works in this case - but has many areas of weakness including recursive map definitions 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
It still inisits on the @ sign in various places - which I don't get...
它仍然存在于各个地方的@符号 - 我没有得到...
adding convert w/ workaround for the ampersand issue
为&符问题添加转换w / workaround
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
adding:
[<DataContract>]
above the record fixes the Ampersand issue.
在记录之上修复了Ampersand问题。