What is the Roc idiomatic way of encoding Dicts to json?
I'm trying to solve the rest-api excercise on Exercism and my first try was to just use the Encoding ability of the platform to encode a Dict as JSON.
Getting this error though:
I can't generate an implementation of the Encoding ability for
List User
In particular, an implementation for
Dict Str F64
cannot be generated.
My next idea was to use some json encoding primitives like in Elm but this does not seem to be an idiomatic way to do it in Roc? I could not find any Elm like encoding functions anyway.
Is string concatenation really my only option to build json objects with dynamic field names in Roc right now?
Encode and Decode before Dict and Set where really proper types. They need to be expanded
We actually have a proposal and partial implementation of a much more flexible implementation. Sadly, I think it is currently stuck in some lambaset bugs.
That said, the current version of encode and decode definitely could be expanded to support dict and set directly.
New encode and decode idea discussed in #ideas > Revamped Encode and Decode.
I should probably circle back to the partial implementation to see if I can push it farther
Thanks for the link to the Revamped Encode and Decode discusson. Interesting read indeed, hope you will manage to finish it.
Do you know if there is any other (possibly Elm style) Roc json-libraries in the wild? I really like the simplicity of Elms approach to json encoding / decoding and wouldn't mind using the same approach in Roc.
The Roc Encode / Decode stuff is still a bit magical to me. :-)
I don't know if any other library for json
it would definitely be possible to port elm-json-decode-pipeline to Roc!
you could even use record builder syntax to make it even nicer
Cool, I need to read up on that record builder syntax now. :-)
That one is in the tutorial
Feel free to ask anyone about it, but I in particular have good experience building interfaces with them
I think this would be very useful in code generated from an API contract. Like type-safe GraphQL or OpenAPI clients. You don’t want those to use the derived Encode/Decode abilities.
Couldn't GraphQL just generate the correct records and then use decode and encode?
From my perspective I don't really think that decoding JSON => Record is very useful except for very simple cases. Optimizing for simple decoding of json into record structures only means (if I understand it correctly) that I need to have a "simple" record typ that represents the structure of the json document 1-to-1 and then parse this json-record into my domain types. For this more advanced case its just much simpler to write a elm-style decoder from json directly to my domain model imho.
There could of course be something I'm missing in this Encode/Decode architecture. I must confess that I don't have all the concepts totally clear in my head right now. :)
I personally struggled a bit with elm JSON decoders in terms of the learning curve.
Definitely powerful but a bit of a mind bending experience for me.
I struggled with json decoders in Elm too. :-)
I do think that json => record is very good for beginners and simple usecases. But the moment you want something that is not easily represented 1-to-1 as a record it starts to get more complicated to not use elm style decoders imho.
A good example is sum-types and how the "tag" are represented in the json structure.
Yeah, exactly. I think this applies primarily to clients, servers are more likely to match because they own the structure.
When you’re building a client, it’s quite common that (even if it’s within the same company) the structure defined in the API is inconvenient for your use case. Things might be too nested, too general, have confusing names, or abuse primitives.
Withdillonkearns/elm-graphql
, I can deal with all of that complexity at the border right where I define the query selection.
APIs become messy over time as they evolve, but a client is often relying on a subset or a specific point in time where certain properties hold true. This is the mismatch that warrants this approach.
Another thing this applies to is database schemas, which is why I designed the roc-pg
’s selection builder after elm-graphql
’s. You can rename columns, map them, or even change the structure at the query.
All that said, that applies at a certain level of complexity and depth. There’s so many clients that only need to work with a few fields in mostly flat structures, and the derived abilities would work great :grinning:
I'd be curious which of these critiques apply to serde from rust. It is used very comprehensively in the rust ecosystem. I think people rarely reach for something else. (Graphql maybe being special due to flexible queries). But for most things, I think it full covers everything. So encode and decode should theoretically as well.
The jackson library is very popular in Java and it is similar in that your json is serialized and deserialized based on the fields on your objects so usually the structure of your json and objects is very closely related. That being said, there is a lot of customization you can do that makes it more flexible. The kind I end up using most is field level customizations like specifying how a date should be formatted.
I would argue that the way json serialization are done in Java (with for example the Jackson library) more has to do with tradition and Javas background in OOP than it has to do with being a "good" way. :smile:
I have seen (and produced myself) lots of Java code with tons and tons of annotations polluting the carefully crafted domain objects just to configure how the objects properties should serialize to json. The rationale for all this noise is that its "just configuration" and somehow configuration is always better than code...
Imho, simple Elm-style transformation functions that maps the domain objects to it's json representation often is way more compact, more flexible and easier to read than all the annotation mess and I really wish these functional approaches would find its way into Java land. Tradition is hard to change though...
Since Roc aims to be a functional language I really hope Roc will take the more functional approach, or at least have good support for both approaches.
I'm sure both approaches can work and will be supported. I think with decode instead of annotations it would be specified in the types. Occasionally with custom abilities impls.
Like you might have a Nullable I64
json field that allows for the field to be missing.
I'm mostly just curious where decode will fall short and why. Is it personal preference or something more fundamental. I probably should look more into where serde falls short cause decode is based on serde.
I don't think either approach is more or less functional than the other :big_smile:
one is more flexible but also more repetitive in common cases
that's the main tradeoff I see in terms of usage
there's also a major learning curve gap between them for common cases though
I'm mostly just curious where decode will fall short and why. Is it personal preference or something more fundamental.
For me it's a personal preference of wanting to have a clear separation of the domain model and it's representation as json. That's why I don't usually find generic json => record mappings (and the other way around) that useful. In Java that means I often end up with a set of simple classes representing the wanted json-structure (which is serialized/deserialized using jackson or similar) and then a set of transformation functions to and from the domain-model.
Since the json-model is just something thats needed by Jackson its just boilerplate and I would rather use an Elm style decoder/encoder pattern to do the mapping to/from json directly. For me this is a way more ergonomic / flexible pattern than the traditional style using annotations and reflection through jackson.
But I do understand that for simple usecases and beginners this may be a big hurdle.
Like you might have a
Nullable I64
json field that allows for the field to be missing.
This is actually a good example of what I DON'T want : smile:
I do not want my domain types be polluted by stuff that only makes sense in the context of how it´s represented as json.
Fair enough. I think the standard for more complex inputs and something like serde is to represent the input exactly, map it into the happy land of a typed language with all the worts like Nullable
(this guarantees it is fully represented and subsequent logic handles all cases), map it to a nice struct for domain use and without worts.
So you still have a domain type and logic. It is just split into two parts.
I've been thinking a bit and realised that the only thing I really need is a way to Decode / Encode json-text into a general ADT like this:
Json : [
True,
False,
Null,
Number F64,
String Str,
Array (List Json),
Object (List (Str, Json)),
]
Do you think this is something that could be achieved using the Roc Decode/Encode functions and the existing Json library? Or maybe something as general as this is out of the scope for the Decode/Encode feature?
With a custom implementation of the ability, that should be doable. Then you could encode/decode any json.
That said, may be faster in many cases to lazily decode json. That way you only load the parts of the message you care about. That would not be possible with encode/decode.
Not sure about what you mean by lazy but being able to just extract specific parts of a json-document is really valuable and something I do a lot. I have not realised that this could be a problem with Decode though.
Do you mean that the record structure must match the json structure exactly (like all fields must be included) to be able to decode it?
What I mean is Decode will parse the entire json document into a roc record. If the document is 100MB and you want to access 1 specific field, that is a huge waste of time parsing the entire document.
A lazy parser could do the minimal work to grab the one field. Then if you need a second field, it would again do minimal work to grab the second field.
Really depends on the use case though. If you are always gonna access most fields, this won't matter.
In tag terms, it might represent json as:
Json : [
True,
False,
Null,
Number F64,
String Str,
Array (List Json),
Object (List (Str, Json)),
Lazy ({} -> Json),
]
This way, it can stop parsing part way instead of parsing everything.
I'm not an expert on parsing but I'm not sure how one could parse a JSON file without reading the complete document? I mean, if just the last closing curly bracket is missing the document is invalid.
2 main options:
For example, if I want to get foo.bar
, I can still do so from this:
{
"baz": []
"foo": {
"something": "abc",
"bar": 42,
"other": null,
Still not sure how one could do 2 without proper parsing but as I said, I'm not an parsing expert. :smile:
To me it seems like there is an opportunity to fill a missing space in the Roc ecosystem by writing a generic JSON parser library ( json text => tag union ADT) that can be used as a building block for other libraries like for example Elm style json-decoder / encoder.
I'm tempted to have a go at it to be honest. Would be a great way to learn more about Roc as a programming language too.
by writing a generic JSON parser library
That could definitely be useful for when decode isn't enough. That said, Decode should be able to generate the tag you shared above.
Still not sure how one could do 2 without proper parsing but as I said
You have to be delaying some of the errors if you want max perf. So it is a fundamental trade off. That said, validating an entire json has correct syntax can still be faster than actual parsing the json into a nested tag
That said, Decode should be able to generate the tag you shared above.
Cool, I'll have a look. It it is not obvious for me where to start though.
Is it the DecoderFormatting
interface that i should implement to make this work?
You would implement Decoding
then using the existing roc json decoder in combination. It would provide ways to decode all of the primitives. You need to map them to the the generic tag structure.
Ok, I think I understand how one could use the DecoderFormatter from roc -json to parse the primitives even though it feels a little bit backwards to use parsers named after Roc primitives to parse json elements. But thats just a consequence of Encode/Decode being an generalised abstraction of serialising Roc types I guess.
Yeah...
You also can just write a direct parser
Might be more reasonable with your goals
Yes I think I'll aim for the simplest possible parser from text => ADT that can be used to build higher level features on as a proof of concept.
I read in another thread something about implementing simdjson as a very fast built in support for JSON in the language itself.
If that feature could be used to provide the ADT a parser built with Roc would be obsolete pretty fast :-)
I think @Monica has made one of these for a talk she gave recently on Roc.
I would like to include decoding into a generic ADT JsonValue into roc-json too, I think it will be helpful.
https://github.com/roc-lang/book-of-examples/pull/68
Cool, Monica seems to like elm style decoding/encoding as much as I do. :smile:
As far as I could see Monicas example left out the step of parsing json text to the the Json ADT (or tag union which I understand is the correct term in Roc), right?
I think that makes the case for a library (or a bultin way) of generic json parsing to ADT in Roc even stronger. Anyone that reads that example and thinks, cool I want to use that technique in may app, will otherwise inevitably be forced to first implement his own json parser. That would be really confusing for a beginner I think...
Another case thats a confusing for a Roc beginner like me is the RestApi exercise on Exercism. You can't solve it using Roc's bultin Encode/Decode but instead have to resort to string concatenation hacks just to serialise a json object. This certainly is not something that makes Roc shine compared to other languages. I was really disappointed myself by this (and the reason why I started this thread in the first place).
But I should stop whining now and instead start implementing my json parser :smile:
You can't solve it using Roc's bultin Encode/Decode
Is this only due to dictionaries not encoding/decoding or are there other issues for that exercise specifically?
No, I think it's only the Dict encoding that's missing.
Another case thats a confusing for a Roc beginner like me is the RestApi exercise on Exercism. You can't solve it using Roc's bultin Encode/Decode but instead have to resort to string concatenation hacks just to serialise a json object. This certainly is not something that makes Roc shine compared to other languages. I was really disappointed myself by this (and the reason why I started this thread in the first place).
Perhaps we should take out this exercise until dict encoding/decoding is fixed @Aurélien Geron?
Niklas Konstenius said:
Cool, Monica seems to like elm style decoding/encoding as much as I do. :smile:
As far as I could see Monicas example left out the step of parsing json text to the the Json ADT (or tag union which I understand is the correct term in Roc), right?
I think that makes the case for a library (or a bultin way) of generic json parsing to ADT in Roc even stronger. Anyone that reads that example and thinks, cool I want to use that technique in may app, will otherwise inevitably be forced to first implement his own json parser. That would be really confusing for a beginner I think...
Sorry bit late to the party - yeh my example is strictly Json decoding from a tag union/ADT representation into a domain model, along with some helper functions to abstract away the error handling bits.
Anton said:
Perhaps we should take out this exercise until dict encoding/decoding is fixed Aurélien Geron?
The platform does not allow a track to delete an exercise, but we can mark it as deprecated: it will make no change to anyone who has already started the exercise, but it will hide it from everyone else.
I see two other options:
.docs/instructions.append.md
, so users know what to expect and they can skip the exercise if they want to.RestApi.roc
I think I would prefer alternative 1, but it's not a strong opinion, I'm happy to go any direction here.
we could also prioritize fixing that :grinning_face_with_smiling_eyes:
Yeah, it would be really nice to have Dict supported. I thought someone worked on it for a bit, though I've not been able to find the old PR or branch (or remember who). I thought it was @Isaac Van Doren??
Is it a big job to do? I don't have much idea in that part of the compiler
I took a stab at decoding tag unions not Dict, but I agree that fixing it sounds like a good approach :smiley:
Decoding Tag Unions is one of my most wanted issues. If you could solve it in the same run, I would be so thankful.:pleading_face::kissing_cat:
I took a stab at decoding tag unions...
Cool. I'm curious in how the design of decoding tag unions would work from a user perspective?
What kind of structures would be possible to decode? I guess it has to be the "inverse" of the tag
function in EncoderFormatting
in some way?
they can skip the exercise if they want to.
Can exercises be skipped? I know there is some kind of unlocking of exercises mechanism
Yes. For roc (at the Moment) no exercise is locked. You can start with any of them.
Cool. I'm curious in how the design of decoding tag unions would work from a user perspective?
What kind of structures would be possible to decode? I guess it has to be the "inverse" of thetag
function inEncoderFormatting
in some way?
It would be up to the DecoderFormatting implementation (such as roc-json) to decide how to handle the decoding. I think the way that makes the most sense as a start would be to decode the following json into the following roc:
[{ "a": { "field1": "foo" } }, { "b": { "field2": "bar" } }]
[A { field1: "foo" }, B { field2: "bar" }]
But there could be other approaches. Special casing for null
would probably be a good thing to include also.
Last updated: Jul 05 2025 at 12:14 UTC