Wallace
Wallace

Reputation: 17379

F# JSON Type Provider, do not serialize null values

Background

I am using the FSharp.Data JSON Type Provider with a sample that has an array of objects that may have different properties. Here is an illustrative example:

[<Literal>]
let sample = """
    { "input": [
        { "name": "Mickey" },
        { "year": 1928 }
       ] 
    }
"""
type InputTypes = JsonProvider< sample >

The JSON Type Provider creates an Input type which has both an Optional Name and an Optional Year property. That works well.

Problem

When I try to pass an instance of this to the web service, I do something like this:

InputTypes.Root(
    [|
        InputTypes.Input(Some("Mouse"), None)
        InputTypes.Input(None, Some(2028))
    |]
)

The web service is receiving the following and choking on the nulls.

{
  "input": [
    {
      "name": "Mouse",
      "year": null
    },
    {
      "name": null,
      "year": 2028
    }
  ]
}

What I Tried

I find that this works:

InputTypes.Root(
    [|
        InputTypes.Input(JsonValue.Parse("""{ "name": "Mouse" }"""))
        InputTypes.Input(JsonValue.Parse("""{ "year": 2028 }"""))
    |]
)

It sends this:

{
  "input": [
    {
      "name": "Mouse"
    },
    {
      "year": 2028
    }
  ]
}

However, on my real project, the structures are larger and would require a lot more conditional JSON string building. It kind of defeats the purpose.

Questions

As a point of comparison, the Newtonsoft.JSON library has a NullValueHandling attribute.

Upvotes: 2

Views: 611

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243051

I don't think there is an easy way to get the JSON formatting in F# Data to drop the null fields - I think the type does not clearly distinguish between what is null and what is missing.

You can fix that by writing a helper function to drop all null fields:

let rec dropNullFields = function
  | JsonValue.Record flds ->
      flds 
      |> Array.choose (fun (k, v) -> 
        if v = JsonValue.Null then None else
        Some(k, dropNullFields v) )
      |> JsonValue.Record
  | JsonValue.Array arr -> 
      arr |> Array.map dropNullFields |> JsonValue.Array
  | json -> json

Now you can do the following and get the desired result:

let json = 
  InputTypes.Root(
      [|
          InputTypes.Input(Some("Mouse"), None)
          InputTypes.Input(None, Some(2028))
      |]
  )

json.JsonValue |> dropNullFields |> sprintf "%O"

Upvotes: 4

Related Questions