-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
encoding/json: the Decoder.Decode API lends itself to misuse #36225
Comments
I don't think that's a bug, or misuse, I actually depend on it in a project where there's a json header then there's other data. This change would break a lot of working code. |
The only The call to the |
A |
@OneOfOne, I'm not proposing that we change the From my observation, the source of misuse is due to:
The first issue could be alleviated by adding: func UnmarshalReader(r io.Reader, v interface{}) error where The second issue could be alleviated by having an options pattern that doesn't require the creation of a streaming @bcmills, a
|
That would work too. So that gives three patterns that should suppress the warning:
(1) and (2) suppress the warning if the user has read enough of the input to detect either EOF or an earlier syntax error. (3) suppresses the warning if a non-JSON blob follows the JSON value. |
Or a Decoder option to mark it as being single-object only. That way fixing existing instances of this problem (of which there are many, including all over my own code) involves just adding a line, instead of refactoring. |
I like how concise using a decoder often is:
It would be nice if the fix didn't require breaking up the line. I like how @dsnet's UnmarshalReader suggestion offers something similarly concise:
We could also use an alternative Decode method:
|
To give further evidence that this is a common issue, I'm one of the I'm personally in favour of I think a vet check would also be useful, because many existing users might not realise that this common json decoding pattern is buggy. Coupled with |
Maybe a method like |
Shouldn't this be a concrete proposal (with possible variations)? The issue description doesn't seem actionable. |
@networkimprov usually, a proposal brings forward a specific solution to be implemented. It seems like the main point of this issue was to bring up the problem, so the proposal could come later if needed. |
Time and again, I've seen issues closed and further discussion directed to golang-nuts/dev because the item isn't actionable. |
I agree with a new method. It re-use options for |
I suggest the name DecodeFull, it is more consistent with ReadFull. |
I agree that |
Remove this entirely, don't let close-to–duplicate functionality lying around. Especially one that invites misuse. |
I don't want to bikeshed names too hard, but my suggestion would be (Also, this is a function name in C#'s LINQ, where it signifies that an IEnumerable contains a single thing, and throws if not, which feels similar.) |
Removing Let's avoid bikeshedding on names; it brings unnecessary noise to the discussion. Bikeshedding can occur in future CLs that implement this assuming we should do anything about this. To summarize the discussion so far, there seems to be 3 (non-mutually exclusive) approaches:
An issue with approaches 1 or 2 is that it assumes that Personally, I like approach 2 and 3. |
I think the io.EOF issue is a showstopper, unless a new function/method takes a size argument, which could be zero or -1 for read-to-eof. Maybe add the list of solutions to the issue text, and retitle it "proposal: ..." ? I think that places it on the proposal review committee's agenda... |
If the code doing the decoding knows the length of the data, then they could use an I suspect, though, that the more common case is that the code doesn't know, and doesn't care: It just wants to decode until the first json object is done. If it so happens that you can detect that there's extra (non-whitespace?) data, because it came along with a read you issued anyway, then sure, return an error. But I think most users would be perfectly happy to be a bit sloppy here and have only best-effort "extra data" error detection, as evidenced by the multitude of us happily, obliviously using json.Decoder for years and years. So I guess I would advocate "fixing" this problem by defining it away. FWIW, I am also a fan of approaches 2 and 3. |
@dsnet, I notice that this issue is currently without a milestone. Given #36225 (comment), do you want to turn it into a proposal? |
NewDecoder(r).Decode() can lead to bugs, because it just reads from a reader until the end of the serialised object, but it doesn't have to deplete the reader. If you have multiple JSON objects in this reader, you will miss out. golang/go#36225
A variation on this would be to add a configuration method along the line of |
Has there been any progress on this issue? As further evidence of how widespread the problem is: |
Here's another example, from go-chi/render: https://github.com/go-chi/render/blob/master/decoder.go#L44 |
Hi @bcmills . A couple of years ago in #36225 (comment) you suggested that the next step for this issue is for someone to distill a proposal out of the discussion. That doesn't seem to have happened, but I am willing to do it. Would it be best to create a new issue with that proposal, or add it in a comment on this one? |
@dsnet, myself, and others have been working on a new API for encoding/json for some time - we'll share soon. This issue is fixed in the new design, for example, so I'd recommend not spending time on a proposal for now :) |
@mvdan I'm in no way opposed to a rethink of the API encoding/json (I think it's obvious to anyone who uses it extensively that it is old and has gathered some dust). But I wonder if making this issue dependent on such an effort may be a case of the perfect being the enemy of the good. How confident is your group that an implementation of your design will be incorporated into the go standard library in a timely fashion? An advantage of addressing this issue with a new method on |
You can import the JSON experiment in your packages now if you want: https://github.com/go-json-experiment/json |
@dpw I am of course not opposed to smaller overlapping proposals, but we (the maintainers) also have to decide where to spend the time we have. My personal opinion for encoding/json is that small incremental steps aren't the best path forward. |
@mvdan I appreciate that proposals and other contributions do not review themselves, and maintainers have to decide how to invest their time. But on #56733 I see ongoing discussion, involving go maintainers, about another proposal to add a method to |
I hold to a sentiment similar to @mvdan that I'd rather invest most of my efforts on what's to come rather than small incremental steps on what already is, especially if what's to come resolves many issues beyond just this one. You'll notice that most of my arguments in #56733 are from the background of what's best for JSON in the long run rather than simply what's possible to implement today in the current API. That is, I needed to be mentally confident that it could be implemented in both the current and new API with the same semantics. Even if a proposal is accepted, it doesn't mean that it will be implemented immediately. For example, #48298 has been accepted for over a year but not implemented yet in the current API. However, it is already implemented in the new API that @mvdan alluded to. Neither @mvdan nor I are paid to work on Go, but are simply freelance contributors. Unfortunately, we have to be selective about what we spend our attention towards. |
I also came here because I just wanted to use func UnmarshalWithoutUnknownFields(data []byte, v interface{}) errors.E {
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
err := decoder.Decode(v)
if err != nil {
return errors.WithStack(err)
}
_, err = decoder.Token()
if err == nil || !errors.Is(err, io.EOF) {
return errors.New("invalid data after top-level value")
}
return nil
} I think just having an extra example next to the |
Hi all, we kicked off a discussion for a possible "encoding/json/v2" package that addresses the spirit of this proposal. |
Why is the |
Yes, you can. It is just a bit more readable to me this way. |
I wouldn't say rarely, for example, Facebook and others sometimes use JSON records, which the current API works well for: package main
import (
"encoding/json"
"fmt"
"strings"
)
const s = `
{"month":1,"day":2}
{"month":3,"day":4}
`
func main() {
d := json.NewDecoder(strings.NewReader(s))
for {
var date struct { Month, Day int }
if d.Decode(&date) != nil {
break
}
fmt.Printf("%+v\n", date)
}
} |
I'm observing the existence of several production servers that are buggy because the
json.Decoder.Decode
API lends itself to misuse.Consider the following:
json.NewDecoder
is often used because the user has anio.Reader
on hand or wants to configure some of the options onjson.Decoder
. However, the common case is that the user only wants to decode a single JSON value. As it stands the API does not make the common case easy sinceDecode
is designed with the assumption that the user will continue to decode more JSON values, which is rarely the case.The code above executes just fine without reporting an error and silently allows the decoder to silently accept bad input without reporting any problems.
The text was updated successfully, but these errors were encountered: