I have one simply question: Is it possible to parse F# Map type from json? Because when I try it (With F# Map<string, string>
), it is easy to serialize and it looks how it have to, but when I try to deserialize it is throwing an exception.
Newtonsoft.Json.JsonSerializationException: Unable to find a default constructor to use for type Microsoft.FSharp.Collections.FSharpMap`2[System.Int32,System.String]. Path '1', line 2, position 7.
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewDictionary (Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonDictionaryContract contract, System.Boolean& createdFromNonDefaultConstructor) [0x00000] in <filename unknown>:0
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <filename unknown>:0
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <filename unknown>:0
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType, Boolean checkAdditionalContent) [0x00000] in <filename unknown>:0
And it is deserializing from classic:
Map.ofList [ ("1", "one"); ("2", "two"); ("3", "three") ]
The resulting JSON looks like C# dictionary
{
"1": "one",
"2": "two",
"3": "three"
}
It is serializing without settings (Only indentation). So is it possible to serialize this, or is there some working workaround?
Thanks for answer
Deserialization is the process of reconstructing the object from the byte stream.Python refers to serialization and deserialization by terms pickling and unpickling respectively.
Serialization is the process of converting an object into a stream of bytes to store the object or transmit it to memory, a database, or a file. Its main purpose is to save the state of an object in order to be able to recreate it when needed. The reverse process is called deserialization.
Deserialization is the process of reconstructing a data structure or object from a series of bytes or a string in order to instantiate the object for consumption. This is the reverse process of serialization, i.e., converting a data structure or object into a series of bytes for storage or transmission across devices.
It is a format that encodes the data in string format. JSON is language independent and because of that, it is used for storing or transferring data in files. The conversion of data from JSON object string is known as Serialization and its opposite string JSON object is known as Deserialization.
You can make your own converter to do this. It's a lot of reflection and constructing appropriate generic types, but it can be done.
You first deserialize to a Dictionary<Key, Val>
, then create and fill a List<Tuple<Key, Val>>
manually via reflection (because the Map
constructor requires Tuples
, not KeyValuePairs
), then finally pass that into the Map
constructor.
Not sure if there's an easier way, but this is what I came up with:
open System
open System.Collections
open System.Collections.Generic
open Newtonsoft.Json
let mapConverter = {
new JsonConverter() with
override x.CanConvert(t:Type) =
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<Map<_, _>>
override x.WriteJson(writer, value, serializer) =
serializer.Serialize(writer, value)
override x.ReadJson(reader, t, _, serializer) =
let genArgs = t.GetGenericArguments()
let generify (t:Type) = t.MakeGenericType genArgs
let tupleType = generify typedefof<Tuple<_, _>>
let listType = typedefof<List<_>>.MakeGenericType tupleType
let create (t:Type) types = (t.GetConstructor types).Invoke
let list = create listType [||] [||] :?> IList
let kvpType = generify typedefof<KeyValuePair<_, _>>
for kvp in serializer.Deserialize(reader, generify typedefof<Dictionary<_, _>>) :?> IEnumerable do
let get name = (kvpType.GetProperty name).GetValue(kvp, null)
list.Add (create tupleType genArgs [|get "Key"; get "Value"|]) |> ignore
create (generify typedefof<Map<_, _>>) [|listType|] [|list|]
}
Once you have your converter, then you just pass it into the DeserializeObject
method and JsonConvert
will use it wherever appropriate.
let str = JsonConvert.SerializeObject (Map<_, _> [333, 1234])
JsonConvert.DeserializeObject<Map<int, int>>(str, mapConverter)
The nice thing about doing it this way is that if you've got a big/deep record where your Map
is just a single field, then it'll work with that too--you don't have to go changing your record structure to use Dictionaries
just to support serialization.
This functionality became part of JSON.Net in version 6.0.3. (April 30th, 2014)
But, if you are stuck for some reason using an earlier version then a simplified (and more efficient as less reflection) version of Dax Fohl's version could be:
type mapConvert<'f,'t when 'f : comparison>() =
static member readJson (reader:JsonReader, serializer:JsonSerializer) =
serializer.Deserialize<Dictionary<'f, 't>> (reader)
|> Seq.map (fun kv -> kv.Key, kv.Value)
|> Map.ofSeq
let mapConverter = {
new JsonConverter() with
override __.CanConvert (t:Type) =
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<Map<_, _>>
override __.WriteJson (writer, value, serializer) =
serializer.Serialize(writer, value)
override __.ReadJson (reader, t, _, serializer) =
let converter =
typedefof<mapConvert<_,_>>.MakeGenericType (t.GetGenericArguments())
let readJson =
converter.GetMethod("readJson")
readJson.Invoke(null, [| reader; serializer |])
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With