Thoth.Json icon indicating copy to clipboard operation
Thoth.Json copied to clipboard

Add example of decoder/encoder usage for simple and complexe F# types

Open MangelMaxime opened this issue 5 years ago • 2 comments

Issue by MangelMaxime Friday Nov 16, 2018 at 16:16 GMT Originally opened as https://github.com/MangelMaxime/Thoth/issues/72


Current needs:

  • [ ] Show how to encode/decode DUs with and without cases

type Foo =
    { Name : string }

    static member Decoder =
        Decode.object (fun get ->
            { Name = get.Required.Field "name" Decode.string }
        )

    static member Encoder (x : Foo) =
        Encode.object [
            "name", Encode.string x.Name
        ]

type Bar =
    { Value : int }

    static member Decoder =
        Decode.object (fun get ->
            { Value = get.Required.Field "value" Decode.int }
        )

    static member Encoder (x : Bar) =
        Encode.object [
            "value", Encode.int x.Value
        ]

type MyDUWrapper =
    | FooWrapper of List<Foo>
    | BarWrapper of List<Bar>

    static member Decoder =
        Decode.object (fun get ->
            let typ = get.Required.Raw (Decode.field "type" Decode.string)
            match typ with
            | "FooWrapper" ->
                get.Required.Field "values" (Decode.list Foo.Decoder)
                |> FooWrapper
            | "BarWrapper" ->
                get.Required.Field "values" (Decode.list Bar.Decoder)
                |> BarWrapper
            | unkown ->
                failwithf "`%s` isn't a valid type for MyDUWrapper" unkown

        )

    static member Encoder (x : MyDUWrapper) =
        match x with
        | FooWrapper fooValues ->
            Encode.object [
                "type", Encode.string "FooWrapper"
                "values", fooValues |> List.map Foo.Encoder |> Encode.list
            ]

        | BarWrapper barValues ->
            Encode.object [
                "type", Encode.string "BarWrapper"
                "values", barValues |> List.map Bar.Encoder |> Encode.list
            ]

let fooList =
    [ { Name = "Maxime" }
      { Name = "Robert" }
      { Name = "Hervé" } ]

let barList =
    [ { Value = 27 }
      { Value = 17 }
      { Value = 227 }
      { Value = 257 } ]

let fooListJson =
    fooList
    |> List.map Foo.Encoder
    |> Encode.list
    |> Encode.toString 4

let barListJson =
    barList
    |> List.map Bar.Encoder
    |> Encode.list
    |> Encode.toString 4

let myDUWrapperFooJson =
    FooWrapper fooList
    |> MyDUWrapper.Encoder
    |> Encode.toString 4

let myDUWrapperBarJson =
    BarWrapper barList
    |> MyDUWrapper.Encoder
    |> Encode.toString 4


match Decode.fromString MyDUWrapper.Decoder myDUWrapperFooJson with
| Ok values ->
    Browser.console.log values
| Error msg -> Browser.console.error msg

match Decode.fromString MyDUWrapper.Decoder myDUWrapperBarJson with
| Ok values ->
    Browser.console.log values
| Error msg -> Browser.console.error msg

MangelMaxime avatar Nov 08 '19 14:11 MangelMaxime

Comment by MangelMaxime Friday Nov 23, 2018 at 13:25 GMT


Show how to encode/decode DUs with and without cases

open Thoth.Json
open Fable.Import

type Info =
    { Firstname : string
      Age : int }

    static member Decoder =
        Decode.object (fun get ->
            { Firstname = get.Required.Field "firstname" Decode.string 
              Age = get.Required.Field "age" Decode.int }
        )

    static member Encoder (info : Info) =
        Encode.object [
            "firstname" , Encode.string info.Firstname
            "age", Encode.int info.Age
        ]

type Test =
    | CaseA
    | CaseB of string
    | CaseC of Info
    | CaseD of int * string

    static member Decoder =
        Decode.field "type" Decode.string
        |> Decode.andThen (
            function
            | "caseA" ->
                Decode.succeed CaseA

            | "caseB" ->
                Decode.field "data" Decode.string
                |> Decode.map CaseB

            | "caseC" ->
                Decode.field "data" Info.Decoder
                |> Decode.map CaseC

            | "caseD" ->
                Decode.tuple2 Decode.int Decode.string
                |> Decode.field "data" 
                |> Decode.map CaseD

            | unkown -> 
                sprintf "`%s` is an unkown type" unkown
                |> Decode.fail 
        )

    static member Encoder (v : Test) =
        let typ, data =
            match v with
            | CaseA ->
                "caseA", Encode.nil

            | CaseB txt ->
                "caseB", Encode.string txt

            | CaseC info ->
                "caseC", Info.Encoder info

            | CaseD (age, name) ->
                "caseD", Encode.tuple2 Encode.int Encode.string (age, name)
            
        Encode.object [
            "type", Encode.string typ
            "data", data
        ]


let jsonCaseA =
    CaseA
    |> Test.Encoder
    |> Encode.toString 4

printfn "JsonCaseA:\n%s" jsonCaseA

Decode.fromString Test.Decoder jsonCaseA
|> Browser.console.log

let jsonCaseB =
    CaseB "maxime"
    |> Test.Encoder
    |> Encode.toString 4


printfn "JsonCaseB:\n%s" jsonCaseB

Decode.fromString Test.Decoder jsonCaseB
|> Browser.console.log

let jsonCaseC =
    CaseC { Firstname = "Maxime"; Age = 26 }
    |> Test.Encoder
    |> Encode.toString 4

printfn "JsonCaseC:\n%s" jsonCaseC

Decode.fromString Test.Decoder jsonCaseC
|> Browser.console.log

let jsonCaseD =
    CaseD (26, "Maxime")
    |> Test.Encoder
    |> Encode.toString 4

printfn "JsonCaseD:\n%s" jsonCaseD

Decode.fromString Test.Decoder jsonCaseD
|> Browser.console.log

MangelMaxime avatar Nov 08 '19 14:11 MangelMaxime

Comment by MangelMaxime Friday Nov 23, 2018 at 14:18 GMT


For DUs we could use Decode.onfOf to keep the JSON compact and also make it readible for humans.

    static member Decode =
        Decode.oneOf [
            Decode.field "Contains" (Decode.succeed Contains)
            Decode.field "Defined" (Decode.string |> Decode.map Defined)
            Decode.field "NotDefined" (Decode.tuple2 Decode.int Decode.int |> Decode.map NotDefined)
        ]

MangelMaxime avatar Nov 08 '19 14:11 MangelMaxime