Skip to content

Instantly share code, notes, and snippets.

@eulerfx
Last active January 3, 2016 03:29
Show Gist options
  • Select an option

  • Save eulerfx/8402409 to your computer and use it in GitHub Desktop.

Select an option

Save eulerfx/8402409 to your computer and use it in GitHub Desktop.
Json.NET converted for serializing F# sum types.
/// For (1,"b",3) generates [1,"b",3]
type TupleArrayConverter() =
inherit JsonConverter()
override x.CanConvert(typ) = FSharpType.IsTuple(typ)
override x.WriteJson(writer, value, serializer) =
let values = FSharpValue.GetTupleFields(value)
serializer.Serialize(writer, values)
override x.ReadJson(reader, typ, _, serializer) =
let itemTypes = Util.getTupleElements typ
let deserialize t = serializer.Deserialize(reader, t)
let readElements() =
let rec read index acc =
match reader.TokenType with
| JsonToken.EndArray -> acc
| _ ->
let value = deserialize(itemTypes.[index])
reader.Read() |> ignore
read (index + 1) (value::acc)
reader.Read() |> ignore
read 0 List.empty
match reader.TokenType with
| JsonToken.StartArray ->
let values = readElements() |> Array.ofList |> Array.rev
FSharpValue.MakeTuple(values, typ)
| _ -> failwith "invalid token"
/// For type PingPong = Ping of int | Pong and value Ping 1 genereates "{"Ping":1}"
type UnionCaseNameConverter() =
inherit JsonConverter()
override x.CanConvert(typ) = FSharpType.IsUnion(typ) || (typ.DeclaringType <> null && FSharpType.IsUnion(typ.DeclaringType))
override x.WriteJson(writer, value, serializer) =
let typ = value.GetType()
let caseInfo,fieldValues = FSharpValue.GetUnionFields(value, typ)
writer.WriteStartObject()
writer.WritePropertyName(caseInfo.Name)
let value =
match fieldValues.Length with
| 0 -> null
| 1 -> fieldValues.[0]
| _ -> fieldValues :> obj
serializer.Serialize(writer, value)
writer.WriteEndObject()
override x.ReadJson(reader, typ, _, serializer) =
let fail() = failwith "Invalid token!"
let ensure (t:JsonToken) = if reader.TokenType <> t then fail()
let read (t:JsonToken) =
if (reader.Read() = false || reader.TokenType <> t) then fail()
else reader.Value
let typ = if FSharpType.IsUnion(typ) then typ else typ.DeclaringType
let cases = Util.getAndCacheUnionCases(typ)
ensure JsonToken.StartObject
let caseName = read JsonToken.PropertyName |> string
reader.Read() |> ignore
let caseInfo = cases |> Seq.find (fun c -> c.Name = caseName)
let fields = caseInfo.GetFields()
let caseArgs =
match fields.Length with
| 0 -> Array.empty
| 1 -> [| serializer.Deserialize(reader, fields.[0].PropertyType) |]
| _ ->
let tupleType = FSharpType.MakeTupleType(fields |> Array.map (fun f -> f.PropertyType))
let tuple = serializer.Deserialize(reader, tupleType)
FSharpValue.GetTupleFields(tuple)
FSharpValue.MakeUnion(caseInfo, caseArgs)
module private Util =
let cache (f:'a -> 'b) =
let cache = new System.Collections.Concurrent.ConcurrentDictionary<'a,'b>()
fun k -> cache.GetOrAdd(k, f)
let getAndCacheUnionCases = FSharpType.GetUnionCases |> cache
let getTupleElements : Type -> Type[] = FSharpType.GetTupleElements |> cache
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment