Skip to content

Instantly share code, notes, and snippets.

@sayurin
Last active December 17, 2015 21:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sayurin/5678055 to your computer and use it in GitHub Desktop.
Save sayurin/5678055 to your computer and use it in GitHub Desktop.
Json Serializer. this can serialize F# record type.
module Sayuri.Json
open Microsoft.FSharp.Reflection
open System
open System.Collections
open System.Collections.Generic
open System.IO
open System.Linq.Expressions
open System.Reflection
open System.Runtime.Serialization
open System.Runtime.Serialization.Json
open System.Text
// KeyedByTypeCollection has no TryGetValue, so I reimplement it.
type private KeyedByTypeCollection<'T> (creator : Type -> 'T) =
let dict = new Dictionary<_, _>()
member this.Get key =
match dict.TryGetValue key with
| true, value -> value
| false, _ -> let value = creator key
dict.Add (key, value)
value
#if NET_45
let private dictType = typeof<Dictionary<string, obj>>
#else
// see http://sayurin.blogspot.jp/2008/12/datacontractjsonserializerdictionary.html
[<Serializable>]
type JsonDictionary () =
let dictionary = new Dictionary<string, obj>()
member this.Add (key, value : obj) =
dictionary.Add (key, value)
interface ISerializable with
member this.GetObjectData (info : SerializationInfo, context : StreamingContext) =
for pair in dictionary do
info.AddValue (pair.Key, pair.Value)
interface IEnumerable with
member this.GetEnumerator () =
upcast dictionary.GetEnumerator()
let private dictType = typeof<JsonDictionary>
#endif
let private dictAdd = dictType.GetMethod "Add"
let private converter objType =
if not <| FSharpType.IsRecord objType then id
else
let obj = Expression.Parameter typeof<obj>
let t = Expression.Variable objType
let elementInit (pi : PropertyInfo) =
let value = Expression.Property (t, pi)
Expression.ElementInit (dictAdd,
Expression.Constant pi.Name,
if pi.PropertyType.IsValueType then Expression.Convert (value, typeof<obj>) :> Expression
else value :> Expression)
let assign = Expression.Assign (t, Expression.Convert (obj, objType))
let newDict = Expression.ListInit (Expression.New dictType, FSharpType.GetRecordFields objType |> Seq.map elementInit)
FSharpFunc.FromConverter <| Expression.Lambda<_>(Expression.Block ([t], assign, newDict), obj).Compile()
let private converters = new KeyedByTypeCollection<_>(converter)
let private surogate = { new IDataContractSurrogate with
member this.GetDataContractType ``type`` =
if FSharpType.IsRecord ``type`` then dictType else ``type``
member this.GetObjectToSerialize (obj, _) =
converters.Get <| obj.GetType() <| obj
member this.GetCustomDataToExport (_ : Type, _ : Type) : obj = failwith "not implemented"
member this.GetCustomDataToExport (_ : MemberInfo, _ : Type) : obj = failwith "not implemented"
member this.GetDeserializedObject (_, _) = failwith "not implemented"
member this.GetKnownCustomDataTypes _ = failwith "not implemented"
member this.GetReferencedTypeOnImport (_, _, _) = failwith "not implemented"
member this.ProcessImportedType (_, _) = failwith "not implemented" }
#if NET_45
let private settings = new DataContractJsonSerializerSettings(DataContractSurrogate = surogate,
EmitTypeInformation = EmitTypeInformation.Never,
UseSimpleDictionaryFormat = true)
let private serializers = new KeyedByTypeCollection<_>(fun t -> new DataContractJsonSerializer(t, settings))
#else
let private serializers = new KeyedByTypeCollection<_>(fun t -> new DataContractJsonSerializer(t, null, Int32.MaxValue, false, surogate, false))
#endif
let serialize (obj : 'T) =
use memory = new MemoryStream()
(serializers.Get typeof<'T>).WriteObject (memory, obj)
Encoding.UTF8.GetString <| memory.ToArray()
// Usage
// type Record = { i : int; str : string }
// printfn "%s" <| serialize [| { i = 0; str = "abc" }; { i = 1; str = "def" }; |]
// -> [{"i":0,"str":"abc"},{"i":1,"str":"def"}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment