-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for legacy string tag option behavior
In v1, the `string` tag option would stringify bools and strings. Add an option to support this behavior in v2.
- Loading branch information
Showing
4 changed files
with
382 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -126,7 +126,7 @@ func makeBoolArshaler(t reflect.Type) *arshaler { | |
} | ||
|
||
// Optimize for marshaling without preceding whitespace. | ||
if optimizeCommon && !xe.Flags.Get(jsonflags.AnyWhitespace) && !xe.Tokens.Last.NeedObjectName() { | ||
if optimizeCommon && !xe.Flags.Get(jsonflags.AnyWhitespace) && !mo.Flags.Get(jsonflags.StringifyBoolsAndStrings) && !xe.Tokens.Last.NeedObjectName() { | ||
xe.Buf = strconv.AppendBool(xe.Tokens.MayAppendDelim(xe.Buf, 't'), va.Bool()) | ||
xe.Tokens.Last.Increment() | ||
if xe.NeedFlush() { | ||
|
@@ -135,6 +135,13 @@ func makeBoolArshaler(t reflect.Type) *arshaler { | |
return nil | ||
} | ||
|
||
if mo.Flags.Get(jsonflags.StringifyBoolsAndStrings) { | ||
if va.Bool() { | ||
return enc.WriteToken(jsontext.String("true")) | ||
} else { | ||
return enc.WriteToken(jsontext.String("false")) | ||
} | ||
} | ||
return enc.WriteToken(jsontext.Bool(va.Bool())) | ||
} | ||
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { | ||
|
@@ -152,8 +159,22 @@ func makeBoolArshaler(t reflect.Type) *arshaler { | |
va.SetBool(false) | ||
return nil | ||
case 't', 'f': | ||
va.SetBool(tok.Bool()) | ||
return nil | ||
if !uo.Flags.Get(jsonflags.StringifyBoolsAndStrings) { | ||
va.SetBool(tok.Bool()) | ||
return nil | ||
} | ||
case '"': | ||
if uo.Flags.Get(jsonflags.StringifyBoolsAndStrings) { | ||
switch tok.String() { | ||
case "true": | ||
va.SetBool(true) | ||
case "false": | ||
va.SetBool(false) | ||
default: | ||
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: fmt.Errorf("cannot parse %q as bool", tok.String())} | ||
} | ||
return nil | ||
} | ||
} | ||
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t} | ||
} | ||
|
@@ -170,7 +191,7 @@ func makeStringArshaler(t reflect.Type) *arshaler { | |
|
||
// Optimize for marshaling without preceding whitespace or string escaping. | ||
s := va.String() | ||
if optimizeCommon && !xe.Flags.Get(jsonflags.AnyWhitespace) && !xe.Tokens.Last.NeedObjectName() && !jsonwire.NeedEscape(s) { | ||
if optimizeCommon && !xe.Flags.Get(jsonflags.AnyWhitespace) && !mo.Flags.Get(jsonflags.StringifyBoolsAndStrings) && !xe.Tokens.Last.NeedObjectName() && !jsonwire.NeedEscape(s) { | ||
b := xe.Buf | ||
b = xe.Tokens.MayAppendDelim(b, '"') | ||
b = append(b, '"') | ||
|
@@ -184,6 +205,14 @@ func makeStringArshaler(t reflect.Type) *arshaler { | |
return nil | ||
} | ||
|
||
if mo.Flags.Get(jsonflags.StringifyBoolsAndStrings) { | ||
b, err := jsontext.AppendQuote(nil, s) // only fails for invalid UTF-8 | ||
q, _ := jsontext.AppendQuote(nil, b) // cannot fail since b is valid UTF-8 | ||
if err != nil && !xe.Flags.Get(jsonflags.AllowInvalidUTF8) { | ||
return err | ||
} | ||
return enc.WriteValue(q) | ||
} | ||
return enc.WriteToken(jsontext.String(s)) | ||
} | ||
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { | ||
|
@@ -203,6 +232,12 @@ func makeStringArshaler(t reflect.Type) *arshaler { | |
return nil | ||
case '"': | ||
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) | ||
if uo.Flags.Get(jsonflags.StringifyBoolsAndStrings) { | ||
val, err = jsontext.AppendUnquote(nil, val) | ||
if err != nil { | ||
return &SemanticError{action: "unmarshal", JSONKind: '"', GoType: va.Type(), Err: err} | ||
} | ||
} | ||
if xd.StringCache == nil { | ||
xd.StringCache = new(stringCache) | ||
} | ||
|
@@ -1015,7 +1050,13 @@ func makeStructArshaler(t reflect.Type) *arshaler { | |
// Write the object member value. | ||
flagsOriginal := mo.Flags | ||
if f.string { | ||
mo.Flags.Set(jsonflags.StringifyNumbers | 1) | ||
if mo.Flags.Get(jsonflags.StringifyWithLegacySemantics) { | ||
if canLegacyStringify(f.typ) { | ||
mo.Flags.Set(jsonflags.StringifyNumbers | jsonflags.StringifyBoolsAndStrings | 1) | ||
} | ||
} else { | ||
mo.Flags.Set(jsonflags.StringifyNumbers | 1) | ||
} | ||
} | ||
if f.format != "" { | ||
mo.FormatDepth = xe.Tokens.Depth() | ||
|
@@ -1154,7 +1195,13 @@ func makeStructArshaler(t reflect.Type) *arshaler { | |
} | ||
flagsOriginal := uo.Flags | ||
if f.string { | ||
uo.Flags.Set(jsonflags.StringifyNumbers | 1) | ||
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) { | ||
if canLegacyStringify(f.typ) { | ||
uo.Flags.Set(jsonflags.StringifyNumbers | jsonflags.StringifyBoolsAndStrings | 1) | ||
} | ||
} else { | ||
uo.Flags.Set(jsonflags.StringifyNumbers | 1) | ||
} | ||
} | ||
if f.format != "" { | ||
uo.FormatDepth = xd.Tokens.Depth() | ||
|
@@ -1225,6 +1272,25 @@ func isLegacyEmpty(v addressableValue) bool { | |
return false | ||
} | ||
|
||
// canLegacyStringify reports whether t can be stringified according to v1, | ||
// where t is a bool, string, or number (or unnamed pointer to such). | ||
// In v1, the `string` option does not apply recursively to nested types within | ||
// a composite Go type (e.g., a array, slice, struct, map, or interface). | ||
func canLegacyStringify(t reflect.Type) bool { | ||
// Based on encoding/json.typeFields#[email protected] | ||
if t.Name() == "" && t.Kind() == reflect.Ptr { | ||
t = t.Elem() | ||
} | ||
switch t.Kind() { | ||
case reflect.Bool, reflect.String, | ||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | ||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, | ||
reflect.Float32, reflect.Float64: | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
func makeSliceArshaler(t reflect.Type) *arshaler { | ||
var fncs arshaler | ||
var ( | ||
|
@@ -1528,7 +1594,7 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { | |
} | ||
// Optimize for the any type if there are no special options. | ||
if optimizeCommon && | ||
t == anyType && !mo.Flags.Get(jsonflags.StringifyNumbers) && mo.Format == "" && | ||
t == anyType && !mo.Flags.Get(jsonflags.StringifyNumbers) && !mo.Flags.Get(jsonflags.StringifyBoolsAndStrings) && mo.Format == "" && | ||
(mo.Marshalers == nil || !mo.Marshalers.(*Marshalers).fromAny) { | ||
return marshalValueAny(enc, va.Elem().Interface(), mo) | ||
} | ||
|
Oops, something went wrong.