F# – Serializing F# Record and Discriminated Union types

I love using F#’s Record and Dis­crim­i­nat­ed Union types, they work nice­ly with pat­tern match­ing inside your F# code and can often alle­vi­ate some of the cer­e­mo­ny involved around cre­at­ing and using a com­plex object hier­ar­chy.

How­ev­er, on the odd occa­sion when you need to seri­al­ize them into JSON/XML/Binary for­mat, it might not be imme­di­ate­ly obvi­ous what you need to do to achieve that goal.

In gen­er­al, I avoid using F# spe­cif­ic types when I require inter­op­er­abil­i­ty with C# and/or sup­port for trans­port as it’s just much more eas­i­ly done with stan­dard CLR types. How­ev­er, in case that’s not an option, I hope this post will give you a sum­ma­ry of the dif­fer­ent ways you can pre­pare your Record/DU types for trans­port.

Record

To seri­al­ize a Record type using the Dat­a­Con­tract­Se­ri­al­iz­er (for XML) or Dat­a­Con­trac­tJ­son­Se­ri­al­iz­er (for JSON), sim­ply dec­o­rate your Record type with Dat­a­Con­trac­tAt­tribute and DataMem­ber­At­tribute like you nor­mal­ly would:

image

After that you can serialize/deserialize a Per­son instance nor­mal­ly although the seri­al­ized XML/JSON would not be as read­able as with oth­er 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:

<Per­son 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 bina­ry seri­al­iza­tion, a Record type is marked with the Seri­al­iz­able attribute by default, which means it can be seri­al­ized with the Bina­ry­For­mat­ter. This is how the Per­son record type looks in reflec­tor:

image

Discriminated Unions

A sin­gle case Dis­crim­i­nat­ed Union such as the one below is seri­al­iz­able by Dat­a­Con­tract­Se­ri­al­iz­er, Dat­a­Con­trac­tJ­son­Se­ri­al­iz­er and Bina­ry­For­m­mat­ter out of the box.

image

The JSON and XML out­put for an instance of this DU looks like this:

JSON :

{“item”:“test”}

XML   :

<Sin­gle­Case­DU xmlns=”http://schemas.datacontract.org/2004/07/”

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

    <item>test</item>

</SingleCaseDU>

A mul­ti-case Dis­crim­i­nat­ed Union on the oth­er hand, requires a lit­tle more work.

image

Whilst it works with Bina­ry­For­mat­ter by default, it will not with Dat­a­Con­tract­Se­ri­al­iz­er and Dat­a­Con­trac­tJ­son­Se­ri­al­iz­er. If you try to seri­al­ize an instance of Mul­ti­Cas­e­DU using either you will get a Seri­al­iza­tionEx­cep­tion say­ing that the type MultiCaseDU.Case1 or MultiCaseDU.Case2 is not expect­ed.

So to make the Mul­ti­Cas­e­DU type seri­al­iz­able with both Dat­a­Con­tract­Se­ri­al­iz­er and Dat­a­Con­trac­tJ­son­Se­ri­al­iz­er you need to sup­ply the nest­ed types (Case1 and Case2) to the known type resolver. Some­thing like this would do the trick:

image

After that, you’ll be able to seri­al­ize instances of Mul­ti­Cas­e­DU:

JSON

Case1:

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

Case2:

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

XML

Case1:

<Mul­ti­Cas­e­DU 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:

<Mul­ti­Cas­e­DU i:type=“MultiCaseDU._Case2”

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

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

 

 

 

For the full exam­ple, includ­ing seri­al­iza­tion and dese­ri­al­iza­tion code, see this gist.