Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F#, Json, WebApi Serialization of Option Types

Tags:

json.net

f#

I am using record types in a F# project that I am exposing to a C# WebApi project. For example:

type Account = {Amount:float; Number:int; Holder:string}

Based on this post and this post, the json is serializaing correctly.

{"Amount":100.0,"Number":1,"Holder":"Homer"}

However, when I add in a option type to the record,

type Account = {Amount:float; Number:int; Holder:string option }

the json becomes unglued.

{"Amount":100.0,"Number":1,"Holder":{"Case":"Some","Fields":["Homer"]}}

I would want the json to look the same as the non-option type record with the serializer being smart enough to take the values and put them in/out of the option type automatically.

Has anyone built a custom formatter to this end? Is there something OOB that I am missing?

Thanks

like image 363
Jamie Dixon Avatar asked Jan 26 '15 13:01

Jamie Dixon


Video Answer


2 Answers

I tried the converter linked in the other answer, and I didn't like the output of other DUs which were not Option. Instead of that, you may want to opt into only changing the behavior on the Option type rather than all DUs.

I found this converter that will change only the behavior for option type to render null on the None option, otherwise the value. The original code/author info can be found here.

open System
open Microsoft.FSharp.Reflection
open Newtonsoft.Json
open Newtonsoft.Json.Converters

type OptionConverter() =
    inherit JsonConverter()

    override x.CanConvert(t) = 
        t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<option<_>>

    override x.WriteJson(writer, value, serializer) =
        let value = 
            if value = null then null
            else 
                let _,fields = FSharpValue.GetUnionFields(value, value.GetType())
                fields.[0]  
        serializer.Serialize(writer, value)

    override x.ReadJson(reader, t, existingValue, serializer) =        
        let innerType = t.GetGenericArguments().[0]
        let innerType = 
            if innerType.IsValueType then (typedefof<Nullable<_>>).MakeGenericType([|innerType|])
            else innerType        
        let value = serializer.Deserialize(reader, innerType)
        let cases = FSharpType.GetUnionCases(t)
        if value = null then FSharpValue.MakeUnion(cases.[0], [||])
        else FSharpValue.MakeUnion(cases.[1], [|value|])

Using the converter is the same as other answer:

let json = JsonConvert.SerializeObject(myObj, new OptionConverter())
like image 94
Kasey Speakman Avatar answered Nov 09 '22 00:11

Kasey Speakman


A custom Json.NET converter that handles option types and single-type discriminated unions does exist (or at least claims to, I only tested the option type case). It can be found here.

Usage:

let act = {Amount= 100.0; Number= 1; Holder= Some "Homer"}
let json = JsonConvert.SerializeObject(act, new IdiomaticDuConverter())
like image 23
Sven Grosen Avatar answered Nov 09 '22 02:11

Sven Grosen