Skip to content

AngelMunoz/JDeck

Repository files navigation

JDeck a System.Text.Json wrapper

JDeck is a Thoth.Json-like Json decoder based on System.Text.Json in a single file with no external dependencies. Plays well with other libraries that use System.Text.Json like FSharp.SystemTextJson

Note: While JDeck has no dependencies to start working right away, it is recommended to use FsToolkit.ErrorHandling

Usage

For most F# types, you can use the Decode.auto function to decode JSON as shown below:

#r "nuget: JDeck, 1.0.0-beta-*"

open JDeck

type Person = {
  name: string
  age: int
  emails: string list
}
let json = """{"name": "Alice", "age": 30, "emails": ["[email protected]", "[email protected]"] }"""

let result: Result<Person, DecodeError> = Decoding.auto(json)

match result with
| Ok person -> printfn $"Person: %A{person}"
| Error err -> printfn $"Error: %A{err}"

In cases where the data is inconclusive, you deserialize Discriminated Unions or does not play well with F# immutability, you can create a manual decoder.

#r "nuget: JDeck, 1.0.0-beta-*"

open System.Text.Json
open JDeck

type Person = {
  Name: string
  Age: int
  Emails: string list
}
type ServerResponse = { Data: Person; Message: string }

let personDecoder: Decoder<Person> = fun  person -> decode {
  let! name = person |> Required.Property.get("name", Optional.string)
  and! age = person |> Required.Property.get("age", Required.int)
  and! emails = person |> Required.Property.list("emails", Optional.string)
  return {
    Name = name |> Option.defaultValue "<missing name>"
    Age = age
    // Remove any optional value from the list
    Emails = emails |> List.choose id
  }
}
// Inconclusive data coming from the server
let person = """{"name": null, "age": 30, "emails": ["[email protected]", "[email protected]", null] }"""

let result: Result<ServerResponse, DecodeError> =
  // ServerResponse will decode automatically while Person will use the custom decoder
  Decoding.auto(
    $$"""{ "data": {{person}}, "message": "Success" }""",
    // Include your own decoder
    JsonSerializerOptions(PropertyNameCaseInsensitive = true) |> Decode.useDecoder personDecoder
  )

match result with
| Ok person -> printfn $"Person: %A{person}"
| Error err -> printfn $"Error: %A{err}"

Acknowledgements

Nothing is done in the void, in this case I'd like to thank the following libraries for their inspiration and ideas:

  • Thoth.Json for the inspiration and a cross-runtime solution to JSON decoding, compatible with F#, JS, and Python.
  • FsToolkit.ErrorHandling for the general mechanism for dealing with Result types and Computation expressions