F# - Deserialize JSON with Case-Insensitive properties (System.Text.Json)

Date: 2024-04-15 | create | tech | fsharp | json | dotnet |

JSON Serialization / Deserialization is a common plumbing task in modern web services. Dotnet's primary libraries for JSON handling are mostly geared towards C# so using them in F# is sometimes finnicky and lacking documentation.

In this post we'll go over a simple example of how to deserialize JSON in F# such that the cases of the JSON properties don't need to match the cases of your model.

Want to build fullstack F# web apps? Spin up a fullstack F# app in minutes with CloudSeed.

Deserialize JSON with F#

For this example we'll use a model and JSON with mismatched property cases.

  • Model - All PascalCase (every word capitalized)
  • JSON - Various cases to prove it handles them

Here we have a simple script that attempts to deserialize raw json into our data model.

We utilize JsonSerializerOptions to configure System.Text.Json's JsonSerializer and the PropertyNameCaseInsensitive option set to True to get the case-insensitive property behavior we want.

Code (also available on Replit)

open System
open System.Text.Json

type DeserializeType = 
    {
        PascalCase: string
        CamelCase: string
        AllCapsCase: string
        AlternatingCapsCase: string
        SnakeCase: string // HAM: This won't map cause difference is not capitalization
    }

[<EntryPoint>]
let main argv =
    printfn "Hello World from F#!"

    let jsonString = @"{ 
      ""PascalCase"": ""iampascalcase"", 
      ""camelCase"": ""iamcamelcase"",
      ""ALLCAPSCASE"": ""iamallcapscase"",
      ""AlTeRnAtInGcApScAsE"": ""iamalternatingcapscase"",
      ""snake_case"": ""iamsnakecase""
    }"

    let options: JsonSerializerOptions = 
        (JsonSerializerOptions())
    options.PropertyNameCaseInsensitive <- true

    let data = JsonSerializer.Deserialize<DeserializeType>(jsonString, options)

    printfn "Deserialized Data: %A" data
    
    0 // return an integer exit code

Output:

Hello World from F#!
Deserialized Data: { PascalCase = "iampascalcase"
  CamelCase = "iamcamelcase"
  AllCapsCase = "iamallcapscase"
  AlternatingCapsCase = "iamalternatingcapscase"
  SnakeCase = null }

We can see that each property is correctly mapped EXCEPT for SnakeCase.

The reason is:

  • Property SnakeCase differs from JSON snake_case by a character, not a case thus doesn't get mapped

Take note that this is dangerous - our model says it does not accept nulls yet here we have a silent failure which causes our non-nullable data model to contain a null! This is a very easy way to introduce hard-to-track bugs as now our type system is lying to us.

Next

F# is an incredible (and fun!) programming language but there are often gotchas at the edges where / when it tries to integrate with other technologies with different defaults. Always be careful when integrating with C# / dotnet in particular as they tend to like mutable OO and nullable values.

Q: How are you handling JSON in your F# codebase? What strategies do you use to make it safe and sane?

If you liked this post you might also like:

Want more like this?

The best / easiest way to support my work is by subscribing for future updates and sharing with your network.