Last active
December 17, 2015 21:49
-
-
Save sayurin/5678055 to your computer and use it in GitHub Desktop.
Json Serializer. this can serialize F# record type.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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