// 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) } // 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 FormatDepth int } // DefaultOptionsV2 is the set of all options that define default v2 behavior. var DefaultOptionsV2 = Struct{ Flags: jsonflags.Flags{ Presence: uint64(jsonflags.AllFlags & ^jsonflags.WhitespaceFlags), Values: uint64(0), }, } // DefaultOptionsV1 is the set of all options that define default v1 behavior. var DefaultOptionsV1 = Struct{ Flags: jsonflags.Flags{ Presence: uint64(jsonflags.AllFlags & ^jsonflags.WhitespaceFlags), Values: uint64(jsonflags.DefaultV1Flags), }, } 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) 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) { panic("unknown option") } func (dst *Struct) Join(srcs ...Options) { dst.join(false, srcs...) } func (dst *Struct) JoinWithoutCoderOptions(srcs ...Options) { dst.join(true, srcs...) } func (dst *Struct) join(excludeCoderOptions bool, srcs ...Options) { for _, src := range srcs { switch src := src.(type) { case nil: continue case jsonflags.Bools: if excludeCoderOptions { src &= ^jsonflags.AllCoderFlags } dst.Flags.Set(src) case Indent: if excludeCoderOptions { continue } dst.Flags.Set(jsonflags.Multiline | jsonflags.Indent | 1) dst.Indent = string(src) case IndentPrefix: if excludeCoderOptions { continue } dst.Flags.Set(jsonflags.Multiline | jsonflags.IndentPrefix | 1) dst.IndentPrefix = string(src) case ByteLimit: if excludeCoderOptions { continue } dst.Flags.Set(jsonflags.ByteLimit | 1) dst.ByteLimit = int64(src) case DepthLimit: if excludeCoderOptions { continue } dst.Flags.Set(jsonflags.DepthLimit | 1) dst.DepthLimit = int(src) case *Struct: srcFlags := src.Flags // shallow copy the flags if excludeCoderOptions { srcFlags.Clear(jsonflags.AllCoderFlags) } dst.Flags.Join(srcFlags) if srcFlags.Has(jsonflags.NonBooleanFlags) { if srcFlags.Has(jsonflags.Indent) { dst.Indent = src.Indent } if srcFlags.Has(jsonflags.IndentPrefix) { dst.IndentPrefix = src.IndentPrefix } if srcFlags.Has(jsonflags.ByteLimit) { dst.ByteLimit = src.ByteLimit } if srcFlags.Has(jsonflags.DepthLimit) { dst.DepthLimit = src.DepthLimit } if srcFlags.Has(jsonflags.Marshalers) { dst.Marshalers = src.Marshalers } if srcFlags.Has(jsonflags.Unmarshalers) { dst.Unmarshalers = src.Unmarshalers } } default: JoinUnknownOption(dst, src) } } } 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) {}