Source file src/encoding/json/internal/jsonopts/options.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build goexperiment.jsonv2
     6  
     7  package jsonopts
     8  
     9  import (
    10  	"encoding/json/internal"
    11  	"encoding/json/internal/jsonflags"
    12  )
    13  
    14  // Options is the common options type shared across json packages.
    15  type Options interface {
    16  	// JSONOptions is exported so related json packages can implement Options.
    17  	JSONOptions(internal.NotForPublicUse)
    18  }
    19  
    20  // experimentalFormatTagSupporter is a special option method that allows
    21  // [github.com/go-json-experiment/json.ExperimentalSupportFormatTag]
    22  // to enable experimental `format` tag functionality at runtime.
    23  type experimentalFormatTagSupporter interface{ ExperimentalSupportFormatTag() bool }
    24  
    25  // Struct is the combination of all options in struct form.
    26  // This is efficient to pass down the call stack and to query.
    27  type Struct struct {
    28  	Flags jsonflags.Flags
    29  
    30  	CoderValues
    31  	ArshalValues
    32  }
    33  
    34  type CoderValues struct {
    35  	Indent       string // jsonflags.Indent
    36  	IndentPrefix string // jsonflags.IndentPrefix
    37  	ByteLimit    int64  // jsonflags.ByteLimit
    38  	DepthLimit   int    // jsonflags.DepthLimit
    39  }
    40  
    41  type ArshalValues struct {
    42  	// The Marshalers and Unmarshalers fields use the any type to avoid a
    43  	// concrete dependency on *json.Marshalers and *json.Unmarshalers,
    44  	// which would in turn create a dependency on the "reflect" package.
    45  
    46  	Marshalers   any // jsonflags.Marshalers
    47  	Unmarshalers any // jsonflags.Unmarshalers
    48  
    49  	Format string // valid if jsonflags.FormatTag is set
    50  }
    51  
    52  // DefaultOptionsV2 is the set of all options that define default v2 behavior.
    53  var DefaultOptionsV2 = Struct{
    54  	Flags: jsonflags.Flags{
    55  		Presence: uint64(jsonflags.DefaultV1Flags),
    56  		Values:   uint64(0), // all flags in DefaultV1Flags are false
    57  	},
    58  }
    59  
    60  // DefaultOptionsV1 is the set of all options that define default v1 behavior.
    61  var DefaultOptionsV1 = Struct{
    62  	Flags: jsonflags.Flags{
    63  		Presence: uint64(jsonflags.DefaultV1Flags),
    64  		Values:   uint64(jsonflags.DefaultV1Flags), // all flags in DefaultV1Flags are true
    65  	},
    66  }
    67  
    68  func (*Struct) JSONOptions(internal.NotForPublicUse) {}
    69  
    70  // GetUnknownOption is injected by the "json" package to handle Options
    71  // declared in that package so that "jsonopts" can handle them.
    72  var GetUnknownOption = func(Struct, Options) (any, bool) { panic("unknown option") }
    73  
    74  func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
    75  	// Collapse the options to *Struct to simplify lookup.
    76  	structOpts, ok := opts.(*Struct)
    77  	if !ok {
    78  		var structOpts2 Struct
    79  		structOpts2.Join(opts)
    80  		structOpts = &structOpts2
    81  	}
    82  
    83  	// Lookup the option based on the return value of the setter.
    84  	var zero T
    85  	switch opt := setter(zero).(type) {
    86  	case jsonflags.Bools:
    87  		v := structOpts.Flags.Get(opt)
    88  		ok := structOpts.Flags.Has(opt)
    89  		if !ok && opt == jsonflags.StringifyNumbers && structOpts.Flags.Get(jsonflags.StringTag) {
    90  			return any(true).(T), true // check also whether the option is specified via a `string` tag
    91  		}
    92  		return any(v).(T), ok
    93  	case experimentalFormatTagSupporter:
    94  		v := structOpts.Flags.Get(jsonflags.FormatTagSupported)
    95  		ok := structOpts.Flags.Has(jsonflags.FormatTagSupported)
    96  		return any(v).(T), ok
    97  	case Indent:
    98  		if !structOpts.Flags.Has(jsonflags.Indent) {
    99  			return zero, false
   100  		}
   101  		return any(structOpts.Indent).(T), true
   102  	case IndentPrefix:
   103  		if !structOpts.Flags.Has(jsonflags.IndentPrefix) {
   104  			return zero, false
   105  		}
   106  		return any(structOpts.IndentPrefix).(T), true
   107  	case ByteLimit:
   108  		if !structOpts.Flags.Has(jsonflags.ByteLimit) {
   109  			return zero, false
   110  		}
   111  		return any(structOpts.ByteLimit).(T), true
   112  	case DepthLimit:
   113  		if !structOpts.Flags.Has(jsonflags.DepthLimit) {
   114  			return zero, false
   115  		}
   116  		return any(structOpts.DepthLimit).(T), true
   117  	default:
   118  		v, ok := GetUnknownOption(*structOpts, opt)
   119  		return v.(T), ok
   120  	}
   121  }
   122  
   123  // JoinUnknownOption is injected by the "json" package to handle Options
   124  // declared in that package so that "jsonopts" can handle them.
   125  var JoinUnknownOption = func(Struct, Options) Struct { panic("unknown option") }
   126  
   127  func (dst *Struct) Join(srcs ...Options) {
   128  	for _, src := range srcs {
   129  		switch src := src.(type) {
   130  		case nil:
   131  			continue
   132  		case jsonflags.Bools:
   133  			dst.Flags.Set(src)
   134  		case experimentalFormatTagSupporter:
   135  			if src.ExperimentalSupportFormatTag() {
   136  				dst.Flags.Set(jsonflags.FormatTagSupported | 1)
   137  			} else {
   138  				dst.Flags.Set(jsonflags.FormatTagSupported | 0)
   139  			}
   140  		case Indent:
   141  			dst.Flags.Set(jsonflags.Multiline | jsonflags.Indent | 1)
   142  			dst.Indent = string(src)
   143  		case IndentPrefix:
   144  			dst.Flags.Set(jsonflags.Multiline | jsonflags.IndentPrefix | 1)
   145  			dst.IndentPrefix = string(src)
   146  		case ByteLimit:
   147  			dst.Flags.Set(jsonflags.ByteLimit | 1)
   148  			dst.ByteLimit = int64(src)
   149  		case DepthLimit:
   150  			dst.Flags.Set(jsonflags.DepthLimit | 1)
   151  			dst.DepthLimit = int(src)
   152  		case *Struct:
   153  			dst.Flags.Join(src.Flags)
   154  			if src.Flags.Has(jsonflags.NonBooleanFlags) {
   155  				if src.Flags.Has(jsonflags.Indent) {
   156  					dst.Indent = src.Indent
   157  				}
   158  				if src.Flags.Has(jsonflags.IndentPrefix) {
   159  					dst.IndentPrefix = src.IndentPrefix
   160  				}
   161  				if src.Flags.Has(jsonflags.ByteLimit) {
   162  					dst.ByteLimit = src.ByteLimit
   163  				}
   164  				if src.Flags.Has(jsonflags.DepthLimit) {
   165  					dst.DepthLimit = src.DepthLimit
   166  				}
   167  				if src.Flags.Has(jsonflags.Marshalers) {
   168  					dst.Marshalers = src.Marshalers
   169  				}
   170  				if src.Flags.Has(jsonflags.Unmarshalers) {
   171  					dst.Unmarshalers = src.Unmarshalers
   172  				}
   173  				if src.Flags.Has(jsonflags.FormatTag) {
   174  					dst.Format = src.Format
   175  				}
   176  			}
   177  		default:
   178  			*dst = JoinUnknownOption(*dst, src)
   179  		}
   180  	}
   181  }
   182  
   183  // InitializeMultiline sets default options implied by Multiline.
   184  func (s *Struct) InitializeMultiline() {
   185  	if !s.Flags.Has(jsonflags.SpaceAfterColon) {
   186  		s.Flags.Set(jsonflags.SpaceAfterColon | 1)
   187  	}
   188  	if !s.Flags.Has(jsonflags.SpaceAfterComma) {
   189  		s.Flags.Set(jsonflags.SpaceAfterComma | 0)
   190  	}
   191  	if !s.Flags.Has(jsonflags.Indent) {
   192  		s.Flags.Set(jsonflags.Indent | 1)
   193  		s.Indent = "\t"
   194  	}
   195  }
   196  
   197  // ChangedWhitespace reports whether whitespace values have changed.
   198  func ChangedWhitespace(s1, s2 Struct) bool {
   199  	return s1.Flags.Get(jsonflags.Multiline) != s2.Flags.Get(jsonflags.Multiline) ||
   200  		s1.Flags.Get(jsonflags.SpaceAfterColon) != s2.Flags.Get(jsonflags.SpaceAfterColon) ||
   201  		s1.Flags.Get(jsonflags.SpaceAfterComma) != s2.Flags.Get(jsonflags.SpaceAfterComma) ||
   202  		(s2.Flags.Get(jsonflags.Multiline) && (s1.Indent != s2.Indent || s1.IndentPrefix != s2.IndentPrefix))
   203  }
   204  
   205  type (
   206  	Indent       string // jsontext.WithIndent
   207  	IndentPrefix string // jsontext.WithIndentPrefix
   208  	ByteLimit    int64  // jsontext.WithByteLimit
   209  	DepthLimit   int    // jsontext.WithDepthLimit
   210  	// type for jsonflags.Marshalers declared in "json" package
   211  	// type for jsonflags.Unmarshalers declared in "json" package
   212  )
   213  
   214  func (Indent) JSONOptions(internal.NotForPublicUse)       {}
   215  func (IndentPrefix) JSONOptions(internal.NotForPublicUse) {}
   216  func (ByteLimit) JSONOptions(internal.NotForPublicUse)    {}
   217  func (DepthLimit) JSONOptions(internal.NotForPublicUse)   {}
   218  

View as plain text