Skip to content

proposal: encoding/json/v2: exported read-write aggregate Options type #77703

@neild

Description

@neild

The current JSONv2 design contains three packages (encoding/json, encoding/jsontext, and encoding/json/v2), all of which define an Options type for customizing marshal and unmarshal behavior. These three Options types are each an alias to the same type, encoding/json/internal/jsonopts.Options. For example:
https://pkg.go.dev/encoding/json/v2#Options

An Options is either a single option, such as "enable deterministic marshalling" or an aggregate of multiple options created by json.JoinOptions. Options are passed as variadic parameters, and options defined in one package may be used in another.

I do not propose substantial changes to any of the above.


The json.GetOption function allows retrieving the value of an option from an Options. This function takes two parameters: An Options and a setter function. It returns the value of the option that is set by the setter. For example:

opts := json.JoinOptions(
	json.Deterministic(true),
	json.StringifyNumbers(true),
)

// Note that json.Deterministic is a function!
deterministic := json.GetOption(opts, json.Deterministic) // true

I believe that json.GetOption is very clever, but also confusing and unidiomatic. Its implementation is subtle. It is not a conventional design.


JSONv2 includes an internal type which represents a collection of options: encoding/json/internal/jsonopts.Struct. This type can represent all options defined by packages under encoding/json/…, offers efficient read and write access, and is used by most of the JSON internals to pass options around the call stack.

The fact that jsonopts.Struct exists is an indication that there is a need for a simple, compact, mutable, mostly-allocation-free representation of a collection of options. This is the type that the JSON internals use by preference.


I propose replacing json.GetOption with an exported version of jsonopts.Struct.

Specifically:

We rename Options to Option in all JSONv2 packages. A json.Option is now a single option.

We add an encoding/json/jsonopts package with a jsonopts.Options type representing a collection of options:

package jsonopts

// Option is identical in use to the current Options,
// although the definition may be different.
type Option interface {
	JSONOptions(internal.NotForPublicUse)
}

// Options is a collection of options.
//
// Options implements the Option interface,
// and may be passed to functions that take ...Option parameters.
//
// Unlike the current internal/jsonopts.Struct,
// Options is an opaque type with accessors for each option.
type Options struct{}

// Join returns an Options describing the provided options.
func Join(options ...Option) Options

// Join updates opts with the provided options.
func (opts *Options) Join(options ...Option)

// Options has accessor methods for all options.

func (opts Options) AllowDuplicateNames() bool
func (opts Options) SetAllowDuplicateNames(v bool)

func (opts Options) AllowInvalidUTF8() bool
func (opts Options) SetAllowInvalidUTF8(v bool)

// etc., for all options.

That's the proposal. We retain all the convenience of the existing variadic options, but offer a conventional options value type as well.

Converting the above GetOption example to the new API:

var opts jsonopts.Options
opts.SetDeterministic(true)
opts.SetStringifyNumbers(true)

deterministic := opts.Deterministic() // true

Or:

opts := jsonopts.Join(
	json.Deterministic(true),
	json.StringifyNumbers(true),
)

deterministic := opts.Deterministic() // true

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Needs triage

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions