F# – Serializing F# Record and Discriminated Union types

I love using F#’s Record and Discriminated Union types, they work nicely with pattern matching inside your F# code and can often alleviate some of the ceremony involved around creating and using a complex object hierarchy.

However, on the odd occasion when you need to serialize them into JSON/XML/Binary format, it might not be immediately obvious what you need to do to achieve that goal.

In general, I avoid using F# specific types when I require interoperability with C# and/or support for transport as it’s just much more easily done with standard CLR types. However, in case that’s not an option, I hope this post will give you a summary of the different ways you can prepare your Record/DU types for transport.

Record

To serialize a Record type using the DataContractSerializer (for XML) or DataContractJsonSerializer (for JSON), simply decorate your Record type with DataContractAttribute and DataMemberAttribute like you normally would:

image

After that you can serialize/deserialize a Person instance normally although the serialized XML/JSON would not be as readable as with other CLR types:

JSON

Record:

{“Age@”:99,”Name@”:”Yan Cui”}

Class:

{ “Age” : 99, “Name” : “Yan Cui” }

XML

Record:

<FSI_0002.Person xmlns=”http://schemas.datacontract.org/2004/07/”

                            xmlns:i=”‘http://www.w3.org/2001/XMLSchema-instance”>

    <Age_x0040_>99</Age_x0040_>

    <Name_x0040_>Yan Cui</Name_x0040_>

</FSI_0002.Person>

Class:

<Person xmlns=”http://schemas.datacontract.org/2004/07/ClassLibrary1”

             xmlns:i=”http://www.w3.org/2001/XMLSchema-instance”>

    <Age>99</Age>

    <Name>Yan Cui</Name>

</Person>

As for binary serialization, a Record type is marked with the Serializable attribute by default, which means it can be serialized with the BinaryFormatter. This is how the Person record type looks in reflector:

image

Discriminated Unions

A single case Discriminated Union such as the one below is serializable by DataContractSerializer, DataContractJsonSerializer and BinaryFormmatter out of the box.

image

The JSON and XML output for an instance of this DU looks like this:

JSON :

{“item”:”test”}

XML   :

<SingleCaseDU xmlns=”http://schemas.datacontract.org/2004/07/”

                        xmlns:i=”http://www.w3.org/2001/XMLSchema-instance”>

    <item>test</item>

</SingleCaseDU>

A multi-case Discriminated Union on the other hand, requires a little more work.

image

Whilst it works with BinaryFormatter by default, it will not with DataContractSerializer and DataContractJsonSerializer. If you try to serialize an instance of MultiCaseDU using either you will get a SerializationException saying that the type MultiCaseDU.Case1 or MultiCaseDU.Case2 is not expected.

So to make the MultiCaseDU type serializable with both DataContractSerializer and DataContractJsonSerializer you need to supply the nested types (Case1 and Case2) to the known type resolver. Something like this would do the trick:

image

After that, you’ll be able to serialize instances of MultiCaseDU:

JSON

Case1:

{“__type”:”MultiCaseDU.Case1:#FSI_0002.Contracts”,”item”:”test”}

Case2:

{“__type”:”MultiCaseDU._Case2:#FSI_0002.Contracts”}

XML

Case1:

<MultiCaseDU i:type=”MultiCaseDU.Case1″

                     xmlns=”http://schemas.datacontract.org/2004/07/”

                     xmlns:i=”http://www.w3.org/2001/XMLSchema-instance”>

    <item>test</item>

</MultiCaseDU>

Case2:

<MultiCaseDU i:type=”MultiCaseDU._Case2″

                     xmlns=”http://schemas.datacontract.org/2004/07/”

                     xmlns:i=”http://www.w3.org/2001/XMLSchema-instance”/>

 

 

 

For the full example, including serialization and deserialization code, see this gist.