-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
json2: replace encoder with new implementation #25224
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
json2: replace encoder with new implementation #25224
Conversation
Connected to Huly®: V_0.6-24624 |
"none is encoded as null, unless @[omitempty] is specified" Why? |
Yeah, on second thought, that is probably not a good idea. The only benefit would be being more explicit about the fields you provide. But it would increase the output a lot for structs that have a lot of option fields. It would also be different from json. A more reasonable approach would be to always omit none, unless @[required] is specified, given that required fields always need to be initialized. |
Problem with outputting I think you were correct at first - it should be output as |
What about the problem of encoding/decoding objects with duplicated keys? struct Base {
key string
}
struct Other {
Base
key string
} |
Not implemented in this PR, but the encoder should always use the embedded field (same as in json). |
@JalonSolov What do you mean by outputting |
I still think that encoding none as null is a huge waste of processing and memory. Furthermore, the use of x |
Can you please expand a bit about the motivation for the deprecations/changing the name of the interface and the method @Larsimusrex ? |
This would be closer to decoder2, which has I would be open to change, but then we should probably come up with a common scheme for all en/decoders. Also searching for interfaces in vlib most use a Somthinger name: Reader, Hash64er, Canceler... |
@enghitalo perhaps I do not understand something, but imho the current behavior in this PR is fine, and compatible with Details
import json
struct Person {
id int = 99
username string
surname ?string
email ?string
age ?int
// fvalue ?f64 = 999.123 // cgen error
// availible ?bool = true // cgen error
// ignore bool @[skip] // cgen error
}
p := Person{}
dump(p)
res := json.encode(p)
dump(res) produces:
and import x.json2 as json
struct Person {
id int = 99
username string
surname ?string
email ?string
age ?int
// fvalue ?f64 = 999.123 // ok, just commented for parity with old json
// availible ?bool = true // ok, just commented for parity with old json
// ignore bool @[skip] // ok, just commented for parity with old json
}
p := Person{}
dump(p)
res := json.encode(p)
dump(res) with the new encoder, produces the same:
Can you please provide runnable code, not just descriptions, about what you do not agree about, and how it should behave in your opinion instead? (otherwise we may be arguing about the same thing, just using different words, and the PR will be needlessly blocked...) |
No, I do not want to change it, or defend the existing naming. I was just curious about the motivation, since it does represent a small breaking change, but your explanation is reasonable and logical 👍🏻 . |
Ok @spytheman . I'll do my best to try to come up with something tonight. If I can't, please proceed as you see fit |
To use import x.json2 as json
struct Person {
id int = 99 @[required]
username string @[required]
surname ?string @[required]
email ?string @[required]
age ?int @[required]
}
p := Person{
id: 20
username: 'bilbo'
surname: none
email: none
age: none
}
res := json.encode(p)
dump(res) This program has a difference between
Do you think, that the output of the |
JSON has Also in JSON, all fields are output unless
|
The Again, |
The (keep in mind, that the support for option field values evolved after most of the |
import json
struct Person {
id int = 99 @[required]
emptystring string @[omitempty]
username string @[required]
surname ?string @[required]
email ?string @[required]
age ?int @[required]
}
p := Person{
id: 20
username: 'bilbo'
surname: none
email: none
age: none
}
res := json.encode(p)
dump(res) this produces: but with the new encoder, currently:
|
i.e. the new x.json2 encoder currently: In my opinion, I do not think that coupling to I do think that encoding |
Go has no concept of option values though, while we do, and the default for them could be different than that of pure value fields. That said supporting |
That is exactly why it must output the option even when it's none. Otherwise, the decoder will throw an error that the field was not provided, meaning that some structs could be encoded but not decoded back. |
I will fix both. |
I think If |
It already does with decoder2.
I disagree. JSON should be as portable as possible. This assumes that other programming languages have an equivalent for options/none, definitely not a given. It also only clarifies that a field exists, not what it's "normal" type would be. The real solution is just to document what output can be expected from your application. |
In Go, if a field is set to package main
import (
"encoding/json"
"fmt"
)
type Struct1 struct {
Foo int
Bar []string
}
type Struct2 struct {
Foo int
Bar []string `json:"Bar,omitempty"`
}
func main() {
s1 := &Struct1{Foo: 1}
b1, _ := json.Marshal(s1)
fmt.Println(string(b1))
s2 := &Struct2{Foo: 2}
b2, _ := json.Marshal(s2)
fmt.Println(string(b2))
} Output is:
|
@JalonSolov We would add additional encoder options for the omitempty and keep_null attributes. One could define their preferred options and pass them to every encode call. const my_options = json.EncoderOptions {
prettify: true
indent_string: '\t'
keep_null: true
}
//...
json.encode(data, my_options) |
Too many options. In general, Alex says "make it same as Go" when talking about features like this. |
But Go does not support option fields ... i.e V has one more degree of freedom, and imho given that different people prefer different things in different situations (as evidenced by this very PR), having a way to configure what the output should be, is reasonable. |
V is also supposed to be "Simple". Having extra options are rarely a bad thing, and some would likely welcome them, but this seems like a simple enough default... |
Advantage of |
Golang can't marshal (encode) private fields (starting with lowecase). I wonder if V encode/decode takes into account |
@spytheman Also it is still unclear what the default behavior for option fields should be and I don't think this discussion is going anywhere. It also raised a bigger question for options in general. Currently they are always omitted (see below), I guess that would change too? import json
fn main() {
a := [?int(0), 1, none, 3, none]
b := {'a': ?int(0), 'b': ?int(1), 'c': ?int(none), 'd': ?int(3), 'e': ?int(none)}
println(json.encode(a))
println(json.encode(b))
}
// [0,1,3]
// {"a":0,"b":1,"d":3} |
For this array a := [?int(0), 1, none, 3, none] this json would have the same length and data in current positions: [0,1,null,3,null] |
@Larsimusrex Yes, you are right. Yesterday I got: |
To me it is now very clear, that option fields with The array and map examples, are very good points though, since they do demonstrate that omitting It also shows that the new encoder, currently does not handle: import x.json2
a := [?int(0), 1, none, 3, none]
println(json2.encode(a)) currently produces: The map one, leads to a checker error: import x.json2
b := {'a': ?int(0), 'b': ?int(1), 'c': ?int(none), 'd': ?int(3), 'e': ?int(none)}
println(json2.encode(b)) |
The previous implementation also could not encode arrays/maps of options. This is because generics do not support options. fn do_generic[T](t T) {
}
fn main() {
a := ?int(null)
do_generic(a)
} It works for struct fields because they is a separate is_option field in FieldData. Maybe you could find similar workarounds for arrays and maps, but they wouldn't be pretty. |
Imho |
Or keep it consistent, and output If this isn't done, then Options act opposite all other types, where you have to ADD an attribute to get output. |
Well, imho different types exist for one reason, each one with its own particularity |
Yes, that is obviously true. However, it also shouldn't require so much "cognitive space" to figure out which things work which way for something as simple as JSON output. Think of it this way... You've created a struct with no option fields, and you JSON encode it. You see all the fields in the output. Now you add an option field, and JSON encode it again, not paying any attention... until the code reading the JSON fails, and you track it down to that option field not being output. Now you have to research why it wasn't output, and find out you have to add a special attribute just to get it to show up in the JSON. To me, that's needless complexity, when it is much simpler the other way... it's output unless you add an attribute to tell it to be skipped, EXACTLY THE SAME as you would do for any other field. |
@JalonSolov I am willing to bet, that the debugging scenario, that you describe, will happen much less often, compared to the case, where people using x.json2, will have useless Defaults matter a lot. The defaults for option fields, should be sensible for them, because that increases the clarity not just for the writer, but for all the readers of the code too. The For option fields though (which Go lacks), since they do have the ability to have a "safe" I do think, that researching JSON encoders for languages, that do have option values, can be beneficial to bring more light on the problem. |
Since I do doubt that we will reach a consensus, I think that @medvednikov should decide what is preferable, in order to unblock the PR, which in all other aspects looks fine to me, and is a big improvement. |
As I understand, the options are
|
I think Optionals won't be used a lot in json on the other hand, that would require changes in json2 and json and make things complex we can go for the minimal way and keep it as is |
Replaces the json2 encoder with my new implement. Adds supports for aliases. Improve prettify, compatibility with json and stability.
fix: #25115
fix: #24165
partial: #24903
DEPRECATED:
BREAKING:
unless @[omitempty] is specifiedwhen @[required] is specified