Skip to content

Commit

Permalink
chore(internal): implement inline json unmarshalling (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
stainless-bot committed Sep 12, 2023
1 parent 62d4e44 commit c902404
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 5 deletions.
49 changes: 44 additions & 5 deletions internal/apijson/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
}
Expand Down
37 changes: 37 additions & 0 deletions internal/apijson/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions internal/apijson/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type parsedStructTag struct {
required bool
extras bool
metadata bool
inline bool
}

func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) {
Expand All @@ -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
Expand Down

0 comments on commit c902404

Please sign in to comment.