Search code examples
serializationf#discriminated-union

Serializing Discriminated Union in F# using FSharpLu - How do i hide property name from Discriminated Union Types?


i am new to F#, and have a task to serialize a Discriminated Union. That in itself is simple, you can serialize it, and you'll get a nice (ugly) case: fields: set of nodes.

Using the Compact Serializer from FSharpLu (https://github.com/Microsoft/fsharplu) we can get this down to a more manageable format, without Case: Field: nodes. However, instead, we get the name of the Type that was serialized as the nodes name. This is not wanted.

If i have a types such as:

type Entry = {
    id: string
    foo: int
    bar: string
}

type Group = {
    id: string
    entryList: Entry seq
}

type EntryOrGroup =
 | Entry of Entry
 | Group of Group

In the output, an array of EntryOrGroup, i would get each node decorated with its type name, which i do not want.

Is there a way to hide the type name? but still output the desired json nodes?

EDIT: Example of current output:

[
    {
        Group: {
            id: "firstEntry",
            entryList: [
                {
                id: "foobar",
                foo: 12,
                bar: "something"
                },
                {
                id: "barfoo",
                foo: 13,
                bar: "somethingElse"
                }
            ]
        },
        {
        Entry: {
            id: "foofoobar",
            foo: 16,
            bar: "notSomething"
            }
        }
    }
]

Example of Desired Output:

[
    {
        {
            id: "firstEntry",
            entryList: [
                {
                id: "foobar",
                foo: 12,
                bar: "something"
                },
                {
                id: "barfoo",
                foo: 13,
                bar: "somethingElse"
                }
            ]
        },
        {
            {
            id: "foofoobar",
            foo: 16,
            bar: "notSomething"
            }
        }
    }
]

Edit2: I forked the FSharpLu repo, and changed the Compact Serializer to no longer write the start of objects and the end of objects in a Discriminated Union. This achieves the result i'm looking for. However, i don't know right now how i can implement that in my work... Is a forked repo too much to maintain, is it worth it for this tiny change? Hmm...

Changes were from Line 146 of Compact.fs: From:

        else
        let case, fields = getUnionFields value

        match fields with
        // Field-less union case
        | [||] -> writer.WriteValue(convertName case.Name)
        // Case with single field
        | [|onefield|] ->
            writer.WriteStartObject()
            writer.WritePropertyName(convertName case.Name)
            serializer.Serialize(writer, onefield)
            writer.WriteEndObject()
        // Case with list of fields
        | _ ->
            writer.WriteStartObject()
            writer.WritePropertyName(convertName case.Name)
            serializer.Serialize(writer, fields)
            writer.WriteEndObject()

To:

        else
        let case, fields = getUnionFields value

        match fields with
        // Field-less union case
        | [||] -> writer.WriteValue(convertName case.Name)
        // Case with single field
        | [|onefield|] ->
            serializer.Serialize(writer, onefield)
        // Case with list of fields
        | _ ->
            writer.WritePropertyName(convertName case.Name)
            serializer.Serialize(writer, fields)

Solution

  • Here are few alternatives that you can consider if you don't want to write/maintain custom Json.NET converter:

    1. Use some other serialization library

    Two alternatives that give you more control how data is serialized:

    1. Thoth.Json.Net
    2. Chiron

    2. Use data transfer objects that are serialized as you want

        type EntryOrGroupDTO = {
            id: string
            foo: int option
            bar: string option
            entryList: Entry seq option
        }
    
        let dtoData =
            data
            |> Array.map (fun entryOrGroup ->
                match entryOrGroup with
                | Entry entry ->
                    { id = entry.id
                      foo = Some entry.foo
                      bar = Some entry.bar
                      entryList = None }
                | Group group ->
                    { id = group.id
                      foo = None
                      bar = None
                      entryList = Some group.entryList })
    
        let settings =
            JsonSerializerSettings(
                NullValueHandling=NullValueHandling.Ignore,
                Converters=[|CompactUnionJsonConverter()|]
            )
    
        JsonConvert.SerializeObject(dtoData, Formatting.Indented, settings)
    

    3. Unwarp and box

        let objData =
            data
            |> Array.map (fun entryOrGroup ->
                match entryOrGroup with
                | Entry entry -> box entry
                | Group group -> box group)
    
    
        JsonConvert.SerializeObject(objData, Formatting.Indented)