thoth-org/Thoth.Json

How to decode a result with a list of DU?

Closed this issue · 3 comments

I am trying to implement a Command/Event system via Giraffe. The command is a 'turnon 1' and works fine, the result is a Result<Event list, Error>. The json looks like

[
  "Ok",
  [
    [
      "TurnedOnEvent",
      {
        "sessionId": "378cb85c-3aa7-4016-acfe-c371f0b61898"
      }
    ]
  ]
]

What I've tried so far:

    type TurnedOnEvent =
        {
            sessionId: Guid
        }
        static member decode =
            Decode.field "sessionId" Decode.guid |> Decode.map (fun x -> { sessionId = x })
        static member encode (turnOff: TurnedOnEvent) =
            Encode.object [
                "sessionId", Encode.guid turnOff.sessionId
            ]
    type PosEvent =
        | TurnedOnEvent of TurnedOnEvent
    module PosEvent =
        let decodeTurnedOnEvent =
            Decode.field "TurnedOnEvent" TurnedOnEvent.decode |> Decode.map TurnedOnEvent
        let decoder: Decoder<PosEvent> =
            Decode.oneOf [
                    decodeTurnedOnEvent //; decodeTurnedOffEvent
            ]
        let encoder (event: PosEvent) =
            match event with
            | TurnedOnEvent turnOn ->
                TurnedOnEvent.encode turnOn

module PosError =
    
    type ErrorCode =
        | SessionNotFound
        | TerminalNotFound
        
    type Error = {
        Code: ErrorCode
        Msg: string
    }

But how can I use these decoders with the Result decoder and then with the list? Is it enough to declare them as extra decoders, oder do I call them manually?

        let decoder: Decoder<Result<PosEvent, PosError.Error>> =
            Decode.string
            |> Decode.andThen (function
                | "Ok" ->
                    ? Decode.object PosEvent.decodeTurnedOnEvent |> Decode.map (Ok(TurnedOnEvent))
                | "Error" -> 
                    ? Decode.Auto.fromString x
                | _ -> Decode.fail "Syntax error in string")

Something like this, or is this the wrong way? Or would it be bad practice at all?

Hello @Slesa,

The problem with your current decoders is that you are trying to access a field TurnedOnEvent (Decode.field "TurnedOnEvent") when your JSON is not using an object but a list where the first element is the name of the DU and the second is the actual value.

Examples on how to decode such DUs can be found here in the documentation.

Here is an example of how to work with your JSON:

open Fable.Core
open System
open Thoth.Json

let json =
    """
[
  "Ok",
  [
    [
      "TurnedOnEvent",
      {
        "sessionId": "378cb85c-3aa7-4016-acfe-c371f0b61898"
      }
    ]
  ]
]
    """

type TurnedOnEvent =
    {
        sessionId: Guid
    }
    
module TurnedOnEvent =

    let decoder : Decoder<TurnedOnEvent> =
        Decode.object (fun get ->
            {
                sessionId = get.Required.Field "sessionId" Decode.guid
            }
        )

type PosEvent =
    | TurnedOnEvent of TurnedOnEvent

module PosEvent =

    let decoder : Decoder<PosEvent> =
        Decode.index 0 Decode.string
        |> Decode.andThen (function
            | "TurnedOnEvent" -> 
                Decode.index 1 TurnedOnEvent.decoder
                |> Decode.map TurnedOnEvent
            | unknown ->
                sprintf "Unknown type for PosEvent : '%s'" unknown
                |> Decode.fail 
        )

module Envelop =

    let decoder : Decoder<Result<PosEvent list, string>> =
        Decode.index 0 Decode.string
        |> Decode.andThen (function
            | "Ok" ->
                Decode.index 1 (Decode.list PosEvent.decoder)
                |> Decode.map Ok
            | _ ->
                Decode.succeed "Invalid envelop"
                |> Decode.map Error
        )

match Decode.unsafeFromString Envelop.decoder json with
| Ok data ->
    printfn "Numbers of events: %d" data.Length
| Error error ->
    failwith error

REPL link

Thanks a lot. Yes, I ignored the list as I had absolutely no idea how to concatenate all decoders, Envelope - List - DU.

I am closing as I think the issue has been answered.

If you need something else feel free to comment