Skip to content

njlr/thoth-json-codec

Folders and files

NameName
Last commit message
Last commit date

Latest commit

35e76b5 Â· Jul 22, 2023

History

28 Commits
May 22, 2022
May 22, 2022
Oct 27, 2022
Jul 22, 2023
Nov 21, 2022
May 22, 2022
May 22, 2022
May 22, 2022
Oct 27, 2022
May 22, 2022
Oct 27, 2022
May 22, 2022
Oct 27, 2022
May 22, 2022

Repository files navigation

Thoth.Json.Codec

Experimental codec support for Thoth.Json 🧪

Why use codecs?

  • Easier to keep encoding and decoding in sync
  • Less code in many cases
  • Clearer semantics when both encoding and decoding are required

Install

Install from NuGet for Fable or .NET:

# Fable
dotnet add package Thoth.Json.Codec

# .NET
dotnet add package Thoth.Json.Net.Codec

Or using Paket:

# Fable
paket add Thoth.Json.Codec

# .NET
paket add Thoth.Json.Net.Codec

Instructions

This library is built around a simple type definition:

type Codec<'t> =
  {
    Encoder : Encoder<'t>
    Decoder : Decoder<'t>
  }

Remember that a well-formed codec will allow an arbitary number of encoding-decoding round-trips.

First, open the namespace:

#if FABLE_COMPILER
open Thoth.Json.Codec
#else
open Thoth.Json.Net.Codec
#endif

Now you can create a codec from existing encoders and decoders like so:

let codec = Codec.create Encode.string Decode.string

However, it is recommended to use the built-in primitives.

Codec.int
Codec.bool
Codec.string

// etc...

You can encode values like this:

let json =
  123
  |> Encode.codec Codec.int
  |> Encode.toString 2

And decode JSON like this:

let decoded =
  "true"
  |> Decode.fromString (Decode.codec Codec.bool)

Objects

Object codecs, typically used for Records, can be constructed using the objectCodec Computation Expression:

type FooBar =
  {
    Foo : int
    Bar : string
  }

module FooBar =

  let codec : Codec<FooBar> =
    objectCodec {
      let! foo = Codec.field "foo" (fun x -> x.Foo) Codec.int
      and! bar = Codec.field "bar" (fun x -> x.Bar) Codec.string

      return
        {
          Foo = foo
          Bar = bar
        }
    }

The JSON looks like this:

{
  "foo": 123,
  "bar": "abc"
}

Note the use of and!

Variants

Variants, such as Discriminated Unions, should be constructed using the variantCodec Computation Expression:

type Shape =
  | Square of width : int
  | Rectangle of width : int * height : int

module Shape =

  let codec : Codec<Shape> =
    variantCodec {
      let! square = Codec.case "square" Square Codec.int
      and! rectangle = Codec.case "rectangle" Rectangle (Codec.tuple2 Codec.int Codec.int)

      return
        function
        | Square w -> square w
        | Rectangle (w, h) -> rectangle (w, h)
    }

Again, note the use of and!

With the above codec, the case value will be encoded to a property with the name of the tag.

In other words, the JSON will look like:

{
  "square": 16
}
{
  "rectangle": [
    3,
    4
  ]
}

If you prefer an object with tag and value properties, you can do the following:

module Shape =

  let codec : Codec<Shape> =
    variantCodecWithEncoding (TagAndValue ("tag", "value")) {
      let! square = Codec.case "square" Square Codec.int
      and! rectangle = Codec.case "rectangle" Rectangle (Codec.tuple2 Codec.int Codec.int)

      return
        function
        | Square w -> square w
        | Rectangle (w, h) -> rectangle (w, h)
    }

This gives JSON like so:

{
  "tag": "square",
  "value": 16
}
{
  "tag": "rectangle",
  "value": [
    3,
    4
  ]
}

Auto

Codecs can be generated automatically.

type FooBar =
  {
    Foo : int
    Bar : bool
    Baz : string list
  }

module FooBar =

  let codec : Codec<FooBar> = Codec.Auto.generateCodec(CamelCase)

Beware that at this time, the generated codec may not guarantee the round-trip property!

About

Codec support for Thoth.Json

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages