I know Nim has no shortage of JSON options, however since JSON is really fundamental so much web-related work I think having a strong set of high quality options is not a bad thing at all.
Today I wanted to show a new option I've started using for some of my own work called Sunny.
Sunny brings field tags for JSON to Nim that are very similar to those in Go.
I have previously done some work in Go and found field tags to be a pleasant way of working with JSON. They don't solve everything for everyone, but they get really far for very little complexification.
If you know Go's field tags, you already know what I'm talking about and have your own opinion. If you don't, please check out Sunny's readme here for more info.
Here is a quick little example demonstrating the basic idea:
import sunny
type Example = object
superPrivate {.json: "-".}: string
kind {.json: "type".}: string
myValue {.json: "my_value,omitempty".}: int
let instance = Example.fromJson("""{"type":"value","my_value":3}""")
assert instance.kind == "value"
assert instance.myValue == 3
echo Example(superPrivate: "abc", kind: "value", myValue: 0).toJson()
# {"type":"value"}
Basically, field tags let you rename or skip fields, as well as optionally require fields or omit empty fields. Doing this is self-documenting right on the object fields themselves.
You may not want your objects to have this markup on them, in which case other options are a better. I find this is something I quickly got used to with Go and found comfortable so I wanted it to be an option in Nim as well.
Thanks for taking a look!
For better or worse I have chosen to just emulate Go's behavior right now. I know it is not perfect and foreign to Nim / not using Nim to the best advantage however there is value in the easy transfer of familiarity to counterbalance that.
I can know how to use and read the field tags because of my Go experience, whereas if I start doing different things all the familiarity is gone and the ability to use Go examples and docs and discussion goes away too.
It is totally possible to have a -d:sunnyGo and -d:sunnyNim or similar to enable devs to choose their preferred flavor since this is mostly just compile time interpretation.
I'm starting here knowing full-well it won't be what everyone wants, but I do think there are a lot of people with Go familiarity that will at least know immediately what I'm talking about and how to use it.
Thanks though for the suggestion, I do think a more Nim-native approach is worth the thought.
It is totally possible to have a -d:sunnyGo and -d:sunnyNim or similar to enable devs
That will get you into a territory that only the toplevel can use sunny as one dependency might rely on -d:sunnyGo but another needs -d:sunnyNim if you want to support two syntax two pragmas makes more sense. json could be reserved for the Nim variant and jsonGo for easier access for Golang developers, without introducing dialects of this library.
Regarding the multiple pragmas instead of string tags thought, I think seeing a form of it brings out some potential concerns.
In this case things are mostly the same I think:
type Example = object
myField {.json: "my_field,omitempty,string".}: int
type Example = object
myField {.json: "my_field", omitEmpty, asString.}: int
However, if you are not renaming the field, one has lost the context that these pragmas only have any meaning in the context of JSON:
type Example = object
myField {.json: ",omitempty,string".}: int
type Example = object
myField {.omitEmpty, asString.}: int
So then you may need more explicit pragmas:
type Example = object
myField {.jsonOmitEmpty, jsonAsString.}: int
Which brings you to something like this:
type Example = object
myField {.json: "my_field", jsonOmitEmpty, jsonAsString.}: int
Which starts to look quite a bit worse than just what we started with:
type Example = object
myField {.json: "my_field,omitempty,string".}: int
I have not thought hard about the Nim pragmas though so perhaps there is a stronger set of names that could work better?
One possible pragma solution is to do the following.
type JsonFlag = enum
omitEmpty
asString
template json(name: string = "", flags: set[JsonFlag] = {}) {.pragma.}
type Example = object
myField {.json(flags = {omitEmpty, asString}).}: int
myOtherField {.json("bleh").}: float
myOthererField {.json("bleh", {}).}: int