Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doNotSerialize, jsonName pragmas for JSON serialization closes #8104, #10718, also fixes #11415 #11416

Closed
wants to merge 9 commits into from

Conversation

Vindaar
Copy link
Contributor

@Vindaar Vindaar commented Jun 5, 2019

This PR adds the ability to exclude certain object fields from JSON serialization via %, by annotating the field with the {.noserialize.} pragma.

type
  User = object
    name: string
    age: int
    uid {.noserialize.}: int
let user = User(name: "Siri", age: 7, uid: 1234)
let uJson = % user
doAssert not uJson.hasKey("uid")

After discussion on IRC:
https://irclogs.nim-lang.org/05-06-2019.html#19:15:24
@dom96 asked me to create a PR for this.

While working on it I stumbled upon an issue with custom pragmas on variant objects: #11415.

However, to not break things, I also had to extend how typDef is determined in customPragmaNode (see 5da931f). This is a problem for the first test case in tjsonmacro.nim:
https://github.com/nim-lang/Nim/blob/devel/tests/stdlib/tjsonmacro.nim#L9-L45
Line 45 will fail with node is not a symbol. I couldn't really reproduce this without the % proc change, which is why I didn't open an issue for this as well.

Update: add jsonName

I also added a jsonName pragma, to customize the (de)serialization name of an object field.

type
  User = object
    name: string
    age {.jsonName: "userAge".}: int
let user = User(name: "Siri", age: 7)
let uJson = % user
doAssert uJson["userAge"].num == 7

This pragma closes #10718.

@Vindaar Vindaar changed the title allow excluding object fields from JSON serialization #8104, also fixes #11415 allow excluding object fields from JSON serialization closes #8104, also fixes #11415 Jun 5, 2019
Copy link
Contributor

@dom96 dom96 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might wish to do some bikeshedding on the name. noSerialize is fine, but I wonder if we should include "JSON" in the name and maybe come up with a more generic format for this.

Maybe {.json: NoExport.}. That would also allow us to do things like: {.json: "CustomFieldName".} in the future and keep consistency. Of course we can stick with noSerialize for now and deprecate it in the future for something more generic.

proc `%`*[T: object](o: T): JsonNode =
## Construct JsonNode from tuples and objects.
## An object field annotated with the `{.noserialize.}` pragma will not appear
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## An object field annotated with the `{.noserialize.}` pragma will not appear
##
## An object field annotated with the `{.noserialize.}` pragma will not appear

RST rendering will not put a line break unless you include an empty line like I have done above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, thanks! I'm not very experienced with RST.

@@ -363,10 +392,25 @@ proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) {.inline.} =
assert(obj.kind == JObject)
obj.fields[key] = val

template noserialize*() {.pragma.}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible please add documentation to this as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do!

## .. code-block:: nim
## import json
## let number = 10
## let numJson = % number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: no space after %. Please change this below too.

##
## .. code-block:: nim
## import json
## let number = 10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would simplify this and remove this variable, simply use the literal below.

## doAssert numJson.kind == JInt
##
## For objects it may be desirable to avoid serialization of one or more object
## fields. This can be achieved by using the ``{.noserialize.}`` pragma.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use camelCase here (and everywhere): {.noSerialize.}

@Vindaar
Copy link
Contributor Author

Vindaar commented Jun 11, 2019

Sorry, I overlooked your comment. I'll address the comments later today.

I have no strong feelings about the pragma name. Just picked the first thing that came to mind. Although I might actually prefer the {.json: WhatEver.} syntax.

@alehander92
Copy link
Contributor

keep in mind i can have a "NoExport" field in my json, and this might make a bit confusing json: NoExport: yes, i know it doesnt have " but still adds a moment of "let me think about it while i read it

@dom96
Copy link
Contributor

dom96 commented Jun 11, 2019

@alehander42 got any better suggestions?

@Vindaar
Copy link
Contributor Author

Vindaar commented Jun 11, 2019

Ok, I addressed the comments. For now I only changed the name to camelCase instead of changing it completely. I don't want to be the person making that decision.

@disruptek
Copy link
Contributor

I like noSerialize because it's a notation that transcends implementation. JSON is but one of many possible serialization formats and by abstracting the concept, neither the code nor the programmer needs to change to adapt to new formats.

@metagn
Copy link
Collaborator

metagn commented Jul 22, 2019

Don't mean to make things more complicated, just adding to the discussion:

If {.json: "CustomFieldName".} were to be implemented, you could also implement multiple aliases for the same field, as in {.json: ["CustomFieldName1", "CustomFieldName2"].}. You could even support things like field {.json: ["UnparsedInt": string].}: int or field {.json: string.}: int to convert types during marshalling. This would allow for either {.json: [].} or {.json: void.} to mean no serialization at all, but it looks kind of ugly.

If you still want to just use a name, you could still do something like {.json: noserialize.} and have other libraries do {.otherformat: noserialize.} as well. The transient keyword in java is also an option for a name as it serves the same purpose.

@zah
Copy link
Member

zah commented Jul 22, 2019

In our work-in-progress generic serialization library, I've used a pragma called dontSerialize. That's longer than noSerialize, but it's also more proper grammar :P

https://github.com/status-im/nim-serialization/

@dom96
Copy link
Contributor

dom96 commented Jul 23, 2019

@zah doNotSerialize would be better :P

@Vindaar
Copy link
Contributor Author

Vindaar commented Aug 8, 2019

I renamed noSerialize to doNotSerialize and rebased on the current devel.

@Vindaar Vindaar changed the title allow excluding object fields from JSON serialization closes #8104, also fixes #11415 doNotSerialize, jsonName pragmas for JSON serialization closes #8104, #10718, also fixes #11415 Sep 3, 2019
Vindaar and others added 9 commits September 3, 2019 15:21
Adding the `{.noserialize.}` pragma to an object field means the given
field will be ignored when serialized via the `%` proc.
I'm a little puzzled about this. With the change introduced in the
previous commit to add the `noserialize` option for JSON, the first
example in the `tjsonmacro.nim` does not work anymore. The generic
`Point[float]` cannot be serialized, because of a `node is not a
symbol` error. In order to get the type def implementation under the
given case, we have to also get the implementation of the underlying
type.

Maybe this should be fixed in a different way?
Addresses the other comments of the PR too.
@Vindaar
Copy link
Contributor Author

Vindaar commented Sep 3, 2019

Due to @Phillips126 comment in #10718, I saw Araq's comment about adding a jsonName pragma to customize the (de)serialization names.

Since this PR is related anyways I thought I'd attempt to add this as well. Due to the addition of the jsonName pragma, the naming of the doNotSerialize pragma is probably open for bikeshedding again. :P (or if it should just be the result of {.jsonName: "".} instead of having a different pragma for no serialization.

The implementation isn't very pretty unfortunately for the deserialization, because the whole deserialization logic works on the type node obtained via getTypeImpl. The custom pragma information however is only available when calling getImpl on the type symbol. So I ended up implementing this feature by fixing up the code after the macro is done.

edit: Damn, it seems this implementation is too broken right now. I realized that it'd fail e.g. for seq[<object with jsonName>], fixed that to find table didn't work. After each fix I introduced more problems. I might rethink the whole thing again by fixing the actual to implementation after all.
However, the main problem I see is that it depends on the type of an nnkSym as to how one can extract the pragma information. That's a pain.
edit2: Seems like I managed to make it work for all cases I can think of. Will clean it up later and push.

@Vindaar
Copy link
Contributor Author

Vindaar commented Oct 17, 2019

With #12391 now merged, I'll close this PR and rewrite it. Will probably create a new PR once everything is working.

@Vindaar Vindaar closed this Oct 17, 2019
@disruptek
Copy link
Contributor

Any thoughts about a new version of this PR?

@Vindaar
Copy link
Contributor Author

Vindaar commented Apr 20, 2020

@disruptek Sorry, I kinda forgot about it.

I'll try to take a stab at it in the next few days!

@Vindaar
Copy link
Contributor Author

Vindaar commented Apr 25, 2020

I just remembered why I put this on ice. It's because I was waiting for #11526 to be merged.

I just started to reimplement this. doNotSerialize is essentially the same as in this PR. But the jsonName part doesn't seem to be possible right now (without making some changes to the to procedures), because in the current logic I just can't seem to find a way to extract the pragma information from the types in foldObjectBody.
I'd have to extract the type information from the typedesc in to directly. This would then to require to pass that information to initFromJson, which is ugly. Would have to change the signature either of all initFromJson procs (which is bad, because only one needs the replacement information) or only for the object | tuple proc, but then I'd have to make sure to call the correct proc within the macros depending on the symbols type. Makes for ugly code.

So, I'll wait for a bit until the above PR is hopefully merged soon.

@brentp
Copy link
Contributor

brentp commented Jul 13, 2020

bump. this would be quite useful.

@ghost
Copy link

ghost commented Jul 13, 2020

@Vindaar Well yes, you need to modify the .to procedure, you can check my custom implementation which is based on that PR - https://github.com/Yardanico/telenim/blob/master/src/telenim/json_custom.nim (called toCustom here)

@Vindaar
Copy link
Contributor Author

Vindaar commented Jul 13, 2020

@Yardanico Uhm, if you already have a working version of this around, feel free to create a PR for this!
If you don't feel like doing that, I'll take a look at it. I'm still not sure though about the custom pragmas implementation / would rather wait until that PR is merged.

@ghost
Copy link

ghost commented Jul 13, 2020

@Vindaar yeah, we should want until #11526 is merged because I just copied needed parts of it into my json_custom

@Clyybber
Copy link
Contributor

#11526 is now merged :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

JSON library - Unmarshalling json, which contains "_key" key
8 participants