F#

Json.NET Type Converters for the F# Option, List and Tuple Types

Json.NET is a popular JSON serialization library on the .NET Framework. F# contains several types that aren’t supported by Json.NET out of the box because they don’t have direct analogs in C#. To enable support for these types in Json.NET a type provider implementation must be provided for each type. The types addressed in this post are option, list and tuple.

Option

The F# option is a type which explicitly represents the presence or absence of a value and is an example of a monad. It is a discriminated union declared as follows.

In C#, Nullable is an analog for value types. For reference types, an absence of a value is normally represented with a null reference - a billion dollar mistake. Neither the Nullable type or null references however, can provide the elegance of the option type.

In JSON, all values can be null, including .NET value types such as Int32. Therefore, an empty option can be represented in JSON with a null. The JsonConverter implementation for the option follows.

The CanConvert method determines whether the converter supports a give type - an option in this case. The typedefof<T> construct returns the generic type definition of the type T. The WriteJson method serializes an instance of the supported type. In .NET, an empty option is a null reference and is serialized as such. Given the earlier observation that options are discriminated unions, the value of a non-empty option can be extracted using the FSharpValue.GetUnionFields helper method. The ReadJson method determines the type wrapped by the option and deserializes the value as usual. Next it creates an instance of an option value using the FSharpValue.MakeUnion helper method.

List

An F# list represents an immutable series of values of the same type. It is implemented as a linked list and like the option type, it is a discriminated union declared roughly as follows.

This declaration states that a list is either empty or it is a tuple (pair) of a head, which is the first value in the list and a tail which is the remainder of the values in the list. The converter for the list type is defined as follows.

The list is serialized by first being converted to a IEnumerable which Json.NET already supports. Deserializing is achieved by first deserializing into an IEnumerable and then creating a suitable instance of the F# list type. Since a list is a recursive discriminated union, an instance of it is also created recursively with the inner make function which in turn calls FSharpValue.MakeUnion.

Tuple

An F# tuple is a grouping of unnamed and potentially heterogeneous values. While an analog exists in .NET version 4 and above, a custom converter implementation is still required. JSON does not provide direct support for tuples, but they can be represented in several ways.

One way to represent a tuple in JSON is as an array so that the tuple (“hello”,123) would serialize to [“hello”,123]. This representation has the advantage that arrays already have serialization support and all that is required is the construction of a suitable tuple instance upon deserialization. F# provides the FSharpValue.MakeTuple helper method for creating tuple instances of a specified type given an array of objects denoting the values of the tuple. A first attempt might look like this:

Unfortunately, an object array deserialized in this way may not have the correct type for certain values, numeric values in particular. For instance, the tuple (“hello”, 123) is an instance of the tuple type string * Int32. Upon deserialization however, the second element will have type Int64 not Int32. As a result, each array element must be deserialized individually into a type corresponding to the item in the tuple. This can be done as follows.

The FSharpType.GetTupleElements helper method returns an array of types stored by a tuple. The function readElements in the ReadJson method deserializes array elements individually thus ensuring an appropriate type.

Another way to serialize a tuple is to mirror the serialized shape of a C# tuple so that the tuple (“hello”, 123) becomes {“Item1”:”hello”,”Item2”:123}. This representation is more explicit and will easily deserialize into C# tuples. A excellent account of serializing tuples in this format is depicted in Getting Json.NET to Talk F#, Part 1: Tuples.

Summary

F# to C# inter-op isn’t always a thing of beauty. However, the option, list and tuple types are ubiquitous in F# and thus JSON serialization support is essential, especially for enterprise applications. Given these converters, F# types can be used anywhere JSON serialization is required, such as RavenDB which was the motivating factor for these converters. The RavenDB F# client provides generalized support for the union type with a UnionTypeConverter, however the converters in this post result in more idiomatic JSON representations.

Source

The source code for this post can be found on GitHub.

Comments