I have a type that I currently have encoders/decoders for. I want to turn them into elm-codecs but I’m really struggling.
Here’s the Elm type:
type D
= A { f : Int }
| B { b : String }
| C { f : Int }
And here’s the JSON structure :
{"type": "A", "f": 123}
or
{"type": "B", "b": "blah"}
or
{"type": "C", "f": 456}
Here’s how it works currently (on the decoding side):
a Decode.oneOf tries aDecoder, bDecoder and cDecoder
each of those checks the fields it needs, and then (Decode.andThen) asserts that the type field has the right value.
This bit with the “type” being a field of the data object in the JSON, while being a variant in the Elm type, I can’t seem to find a way to make it translate to elm-codec. Is that just normal, that the codec technique requires you to have similar structures on the Elm and JSON sides? Or is there a way? (I tried something with Codec.andThen but got nowhere.)
I think the choice of field that is used to determine the variant (“type” in your case) is hard coded into elm-codec? Looking at the code I think it might be “tag” but I didn’t manage to fully grok it yet.
Yes it gives something like {"tag":"…","args":[…]}.
I was wondering if there was a more advanced way to get around it, maybe redesigning my type differently. (But that would mean quite a bit of refactoring now, and I might not have enough time budget on this project!)
You might just be building part of a larger Codec, where most of it is standard elm-codec, but you need just a few parts completely custom. If elm-codec gives you nothing, then don’t use it and stick with standard encoder+decoder.
Right, I understand. I thought about sticking to elm-codec just for the record part (the variants’ argument). That’s what I started with, the only problem I have is that I don’t know how to/if I can reproduce this constraint on the value of the type field.
I can write this:
Codec.object
(\type f -> { f = f })
|> Codec.field "type" (always "A") Codec.string
|> Codec.field "f" .f Codec.int
|> Codec.buildObject
Encoding works fine, and puts the type in there, but decoding ignores the type field, so it works even in type is different (which defeats the purpose of this field, which is to disambiguate between different types of data which might use the same fields).
Here is an Ellie that I just put together that shows it all:
encoding is successful
decoding from the previously encoded string is successful, but…
decoding is also successful if the type field is nonsense
miniBill/elm-codec is very opinionated when it comes to what the JSON looks like. We ran into problems with that, just like you do, when trying to introduce it at work. Partly because we had to support already existing JSON structures, partly because we read the actual JSON a lot in Postgres and having to do 'args'->0 all the time there was annoying.
So we forked elm-codec into our own version where you can customize the “tag” field name, and put data in whatever fields you like (instead of "args") and a bunch of other little tweaks and changes (like better error messages). I can’t share that code though.
Ok I guess I’m in that sort of case then. It’s not worth the time for me to find a better solution for this specific problem, so I guess that’s it. Thank you both for your help!