From bbfd5cca24036e9c4d282a444158a207b217ac5c Mon Sep 17 00:00:00 2001 From: Stainless Bot Date: Thu, 31 Aug 2023 17:01:26 +0000 Subject: [PATCH] chore(internal): implement inline json unmarshalling --- internal/apijson/decoder.go | 49 +++++++++++++++++++++++++++++++---- internal/apijson/json_test.go | 37 ++++++++++++++++++++++++++ internal/apijson/tag.go | 3 +++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/internal/apijson/decoder.go b/internal/apijson/decoder.go index ad95b82..a55b2aa 100644 --- a/internal/apijson/decoder.go +++ b/internal/apijson/decoder.go @@ -247,6 +247,7 @@ func (d *decoder) newStructTypeDecoder(t reflect.Type) decoderFunc { // map of json field name to struct field decoders decoderFields := map[string]decoderField{} extraDecoder := (*decoderField)(nil) + inlineDecoder := (*decoderField)(nil) // This helper allows us to recursively collect field encoders into a flat // array. The parameter `index` keeps track of the access patterns necessary @@ -278,6 +279,10 @@ func (d *decoder) newStructTypeDecoder(t reflect.Type) decoderFunc { extraDecoder = &decoderField{ptag, d.typeDecoder(field.Type.Elem()), idx, field.Name} continue } + if ptag.inline && len(index) == 0 { + inlineDecoder = &decoderField{ptag, d.typeDecoder(field.Type), idx, field.Name} + continue + } if ptag.metadata { continue } @@ -299,6 +304,45 @@ func (d *decoder) newStructTypeDecoder(t reflect.Type) decoderFunc { collectFieldDecoders(t, []int{}) return func(node gjson.Result, value reflect.Value) (err error) { + if field := value.FieldByName("JSON"); field.IsValid() { + if raw := field.FieldByName("raw"); raw.IsValid() { + setUnexportedField(raw, node.Raw) + } + } + + if inlineDecoder != nil { + var meta Field + dest := value.FieldByIndex(inlineDecoder.idx) + isValid := false + if dest.IsValid() && node.Type != gjson.Null { + err = inlineDecoder.fn(node, dest) + if err == nil { + isValid = true + } + } + + if node.Type == gjson.Null { + meta = Field{ + raw: node.Raw, + status: null, + } + } else if !isValid { + meta = Field{ + raw: node.Raw, + status: invalid, + } + } else if isValid { + meta = Field{ + raw: node.Raw, + status: valid, + } + } + if metadata := getSubField(value, inlineDecoder.idx, inlineDecoder.goname); metadata.IsValid() { + metadata.Set(reflect.ValueOf(meta)) + } + return err + } + typedExtraType := reflect.Type(nil) typedExtraFields := reflect.Value{} if extraDecoder != nil { @@ -367,11 +411,6 @@ func (d *decoder) newStructTypeDecoder(t reflect.Type) decoderFunc { if metadata := getSubField(value, []int{-1}, "Extras"); metadata.IsValid() && len(untypedExtraFields) > 0 { metadata.Set(reflect.ValueOf(untypedExtraFields)) } - if field := value.FieldByName("JSON"); field.IsValid() { - if raw := field.FieldByName("raw"); raw.IsValid() { - setUnexportedField(raw, node.Raw) - } - } return nil } } diff --git a/internal/apijson/json_test.go b/internal/apijson/json_test.go index d1110f0..0c7a42e 100644 --- a/internal/apijson/json_test.go +++ b/internal/apijson/json_test.go @@ -89,6 +89,21 @@ type Union interface { union() } +type Inline struct { + InlineField Primitives `json:"-,inline"` + JSON InlineJSON `json:"-,metadata"` +} + +type InlineArray struct { + InlineField []string `json:"-,inline"` + JSON InlineJSON `json:"-,metadata"` +} + +type InlineJSON struct { + InlineField Field + raw string +} + func init() { RegisterUnion(reflect.TypeOf((*Union)(nil)).Elem(), "type", UnionVariant{ @@ -305,6 +320,28 @@ var tests = map[string]struct { Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)), }, }, + + "inline_coerce": { + `{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}`, + Inline{ + InlineField: Primitives{A: false, B: 237628372683, C: 0x28e, D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, + JSON: InlineJSON{ + InlineField: Field{raw: "{\"a\":false,\"b\":237628372683,\"c\":654,\"d\":9999.43,\"e\":43.76,\"f\":[1,2,3,4]}", status: 3}, + raw: "{\"a\":false,\"b\":237628372683,\"c\":654,\"d\":9999.43,\"e\":43.76,\"f\":[1,2,3,4]}", + }, + }, + }, + + "inline_array_coerce": { + `["Hello","foo","bar"]`, + InlineArray{ + InlineField: []string{"Hello", "foo", "bar"}, + JSON: InlineJSON{ + InlineField: Field{raw: `["Hello","foo","bar"]`, status: 3}, + raw: `["Hello","foo","bar"]`, + }, + }, + }, } func TestDecode(t *testing.T) { diff --git a/internal/apijson/tag.go b/internal/apijson/tag.go index 69f4d7e..812fb3c 100644 --- a/internal/apijson/tag.go +++ b/internal/apijson/tag.go @@ -13,6 +13,7 @@ type parsedStructTag struct { required bool extras bool metadata bool + inline bool } func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { @@ -33,6 +34,8 @@ func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool tag.extras = true case "metadata": tag.metadata = true + case "inline": + tag.inline = true } } return