| // Copyright 2023 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build goexperiment.jsonv2 | |
| package jsonopts | |
| import ( | |
| "encoding/json/internal" | |
| "encoding/json/internal/jsonflags" | |
| ) | |
| // Options is the common options type shared across json packages. | |
| type Options interface { | |
| // JSONOptions is exported so related json packages can implement Options. | |
| JSONOptions(internal.NotForPublicUse) | |
| } | |
| // experimentalFormatTagSupporter is a special option method that allows | |
| // [github.com/go-json-experiment/json.ExperimentalSupportFormatTag] | |
| // to enable experimental `format` tag functionality at runtime. | |
| type experimentalFormatTagSupporter interface{ ExperimentalSupportFormatTag() bool } | |
| // Struct is the combination of all options in struct form. | |
| // This is efficient to pass down the call stack and to query. | |
| type Struct struct { | |
| Flags jsonflags.Flags | |
| CoderValues | |
| ArshalValues | |
| } | |
| type CoderValues struct { | |
| Indent string // jsonflags.Indent | |
| IndentPrefix string // jsonflags.IndentPrefix | |
| ByteLimit int64 // jsonflags.ByteLimit | |
| DepthLimit int // jsonflags.DepthLimit | |
| } | |
| type ArshalValues struct { | |
| // The Marshalers and Unmarshalers fields use the any type to avoid a | |
| // concrete dependency on *json.Marshalers and *json.Unmarshalers, | |
| // which would in turn create a dependency on the "reflect" package. | |
| Marshalers any // jsonflags.Marshalers | |
| Unmarshalers any // jsonflags.Unmarshalers | |
| Format string // valid if jsonflags.FormatTag is set | |
| } | |
| // DefaultOptionsV2 is the set of all options that define default v2 behavior. | |
| var DefaultOptionsV2 = Struct{ | |
| Flags: jsonflags.Flags{ | |
| Presence: uint64(jsonflags.DefaultV1Flags), | |
| Values: uint64(0), // all flags in DefaultV1Flags are false | |
| }, | |
| } | |
| // DefaultOptionsV1 is the set of all options that define default v1 behavior. | |
| var DefaultOptionsV1 = Struct{ | |
| Flags: jsonflags.Flags{ | |
| Presence: uint64(jsonflags.DefaultV1Flags), | |
| Values: uint64(jsonflags.DefaultV1Flags), // all flags in DefaultV1Flags are true | |
| }, | |
| } | |
| func (*Struct) JSONOptions(internal.NotForPublicUse) {} | |
| // GetUnknownOption is injected by the "json" package to handle Options | |
| // declared in that package so that "jsonopts" can handle them. | |
| var GetUnknownOption = func(Struct, Options) (any, bool) { panic("unknown option") } | |
| func GetOption[T any](opts Options, setter func(T) Options) (T, bool) { | |
| // Collapse the options to *Struct to simplify lookup. | |
| structOpts, ok := opts.(*Struct) | |
| if !ok { | |
| var structOpts2 Struct | |
| structOpts2.Join(opts) | |
| structOpts = &structOpts2 | |
| } | |
| // Lookup the option based on the return value of the setter. | |
| var zero T | |
| switch opt := setter(zero).(type) { | |
| case jsonflags.Bools: | |
| v := structOpts.Flags.Get(opt) | |
| ok := structOpts.Flags.Has(opt) | |
| if !ok && opt == jsonflags.StringifyNumbers && structOpts.Flags.Get(jsonflags.StringTag) { | |
| return any(true).(T), true // check also whether the option is specified via a `string` tag | |
| } | |
| return any(v).(T), ok | |
| case experimentalFormatTagSupporter: | |
| v := structOpts.Flags.Get(jsonflags.FormatTagSupported) | |
| ok := structOpts.Flags.Has(jsonflags.FormatTagSupported) | |
| return any(v).(T), ok | |
| case Indent: | |
| if !structOpts.Flags.Has(jsonflags.Indent) { | |
| return zero, false | |
| } | |
| return any(structOpts.Indent).(T), true | |
| case IndentPrefix: | |
| if !structOpts.Flags.Has(jsonflags.IndentPrefix) { | |
| return zero, false | |
| } | |
| return any(structOpts.IndentPrefix).(T), true | |
| case ByteLimit: | |
| if !structOpts.Flags.Has(jsonflags.ByteLimit) { | |
| return zero, false | |
| } | |
| return any(structOpts.ByteLimit).(T), true | |
| case DepthLimit: | |
| if !structOpts.Flags.Has(jsonflags.DepthLimit) { | |
| return zero, false | |
| } | |
| return any(structOpts.DepthLimit).(T), true | |
| default: | |
| v, ok := GetUnknownOption(*structOpts, opt) | |
| return v.(T), ok | |
| } | |
| } | |
| // JoinUnknownOption is injected by the "json" package to handle Options | |
| // declared in that package so that "jsonopts" can handle them. | |
| var JoinUnknownOption = func(Struct, Options) Struct { panic("unknown option") } | |
| func (dst *Struct) Join(srcs ...Options) { | |
| for _, src := range srcs { | |
| switch src := src.(type) { | |
| case nil: | |
| continue | |
| case jsonflags.Bools: | |
| dst.Flags.Set(src) | |
| case experimentalFormatTagSupporter: | |
| if src.ExperimentalSupportFormatTag() { | |
| dst.Flags.Set(jsonflags.FormatTagSupported | 1) | |
| } else { | |
| dst.Flags.Set(jsonflags.FormatTagSupported | 0) | |
| } | |
| case Indent: | |
| dst.Flags.Set(jsonflags.Multiline | jsonflags.Indent | 1) | |
| dst.Indent = string(src) | |
| case IndentPrefix: | |
| dst.Flags.Set(jsonflags.Multiline | jsonflags.IndentPrefix | 1) | |
| dst.IndentPrefix = string(src) | |
| case ByteLimit: | |
| dst.Flags.Set(jsonflags.ByteLimit | 1) | |
| dst.ByteLimit = int64(src) | |
| case DepthLimit: | |
| dst.Flags.Set(jsonflags.DepthLimit | 1) | |
| dst.DepthLimit = int(src) | |
| case *Struct: | |
| dst.Flags.Join(src.Flags) | |
| if src.Flags.Has(jsonflags.NonBooleanFlags) { | |
| if src.Flags.Has(jsonflags.Indent) { | |
| dst.Indent = src.Indent | |
| } | |
| if src.Flags.Has(jsonflags.IndentPrefix) { | |
| dst.IndentPrefix = src.IndentPrefix | |
| } | |
| if src.Flags.Has(jsonflags.ByteLimit) { | |
| dst.ByteLimit = src.ByteLimit | |
| } | |
| if src.Flags.Has(jsonflags.DepthLimit) { | |
| dst.DepthLimit = src.DepthLimit | |
| } | |
| if src.Flags.Has(jsonflags.Marshalers) { | |
| dst.Marshalers = src.Marshalers | |
| } | |
| if src.Flags.Has(jsonflags.Unmarshalers) { | |
| dst.Unmarshalers = src.Unmarshalers | |
| } | |
| if src.Flags.Has(jsonflags.FormatTag) { | |
| dst.Format = src.Format | |
| } | |
| } | |
| default: | |
| *dst = JoinUnknownOption(*dst, src) | |
| } | |
| } | |
| } | |
| // InitializeMultiline sets default options implied by Multiline. | |
| func (s *Struct) InitializeMultiline() { | |
| if !s.Flags.Has(jsonflags.SpaceAfterColon) { | |
| s.Flags.Set(jsonflags.SpaceAfterColon | 1) | |
| } | |
| if !s.Flags.Has(jsonflags.SpaceAfterComma) { | |
| s.Flags.Set(jsonflags.SpaceAfterComma | 0) | |
| } | |
| if !s.Flags.Has(jsonflags.Indent) { | |
| s.Flags.Set(jsonflags.Indent | 1) | |
| s.Indent = "\t" | |
| } | |
| } | |
| // ChangedWhitespace reports whether whitespace values have changed. | |
| func ChangedWhitespace(s1, s2 Struct) bool { | |
| return s1.Flags.Get(jsonflags.Multiline) != s2.Flags.Get(jsonflags.Multiline) || | |
| s1.Flags.Get(jsonflags.SpaceAfterColon) != s2.Flags.Get(jsonflags.SpaceAfterColon) || | |
| s1.Flags.Get(jsonflags.SpaceAfterComma) != s2.Flags.Get(jsonflags.SpaceAfterComma) || | |
| (s2.Flags.Get(jsonflags.Multiline) && (s1.Indent != s2.Indent || s1.IndentPrefix != s2.IndentPrefix)) | |
| } | |
| type ( | |
| Indent string // jsontext.WithIndent | |
| IndentPrefix string // jsontext.WithIndentPrefix | |
| ByteLimit int64 // jsontext.WithByteLimit | |
| DepthLimit int // jsontext.WithDepthLimit | |
| // type for jsonflags.Marshalers declared in "json" package | |
| // type for jsonflags.Unmarshalers declared in "json" package | |
| ) | |
| func (Indent) JSONOptions(internal.NotForPublicUse) {} | |
| func (IndentPrefix) JSONOptions(internal.NotForPublicUse) {} | |
| func (ByteLimit) JSONOptions(internal.NotForPublicUse) {} | |
| func (DepthLimit) JSONOptions(internal.NotForPublicUse) {} |