Skip to content

encoding/json/v2: proposal: require known fields to exist when unmarshaling #76458

@gman-xyz

Description

@gman-xyz

Proposal Details

In its current implementation, json/v2 (and v1, for that matter) does not check whether JSON object names, as per a struct's fields or their respective JSON name tags, do actually exist in the data that is being unmarshaled. For example:

Let our JSON string be:

{
    "a": 123,
    "b": "hello",
}

And our struct:

type Foo struct {
    A int     `json:"a"`
    B *string `json:"b"`
    C int     `json:"c"`
}

Unmarshaling the JSON string into an instance of Foo will result in C being 0. To distinguish "c": 0 from a nonexistent key "c", we'd need to unmarshal again, but this time into an instance of map[string]any and then check for the existence of c. This would be both cumbersome and inefficient because we'd have to unmarshal twice. Consequently, unmarshaling should fail if a given key is not found.

Now let's assume that the existence of each key is mandatory. What if we wanted C to be optional? This could be achieved by introducing two new JSON tags: optional and nullable: This could be achieved by introducing an optional tag:

type Foo struct {
    A int     `json:"a"`
    B *string `json:"b"`
    C *int    `json:"c,optional"`
}

Note that the type of C is now *int instead of int. A value of nil indicates the absence of "c" in the JSON string. As for B, however, a value of nil indicates a corresponding JSON value of null.
So struct fields with single-pointer types must be either optional or nullable. Setting neither tag is ambiguous and should thus cause an error.

To make C both optional and nullable, we'd simply use a double pointer:

type Foo struct {
    A int     `json:"a"`
    B *string `json:"b"`
    C **int   `json:"c,optional"`
}

Since a double-pointer type already indicates that a field has to be optional and nullable, using a tag of just json:"c" should also suffice in the above case.

Unfortunately, the mandatory existence of fields breaks backwards compatibility. Thus, it should be disabled by default and enabled by setting a option, e.g. KnownFieldsMustExist.

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolProposal

    Type

    No type

    Projects

    Status

    Post-proposal

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions