Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserializing F# Map by Json.Net

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

like image 292
user1607808 Avatar asked Feb 13 '14 07:02

user1607808


People also ask

What is deserializing in Python?

Deserialization is the process of reconstructing the object from the byte stream.Python refers to serialization and deserialization by terms pickling and unpickling respectively.

What does serializing and deserializing mean?

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.

What is deserializing an object?

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.

What is deserializing a JSON?

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.


2 Answers

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.

like image 127
Dax Fohl Avatar answered Sep 30 '22 16:09

Dax Fohl


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 |])
}
like image 34
Paul Westcott Avatar answered Sep 30 '22 15:09

Paul Westcott