Skip to content

Commit

Permalink
support emitting slices and maps with non-any values via reflection
Browse files Browse the repository at this point in the history
  • Loading branch information
mumbleskates committed Mar 25, 2024
1 parent e1d07eb commit 2a76dca
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 3 deletions.
67 changes: 64 additions & 3 deletions emit.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,16 +288,77 @@ func (e *emitter) Emit(v interface{}) (err error) {
case error:
return e.emitError(vt)
default:
rv := reflect.ValueOf(&v)
if rv.Type().Elem().Kind() == reflect.Pointer {
rp := rv.Elem() // rp is the reflected pointer value of v
ty := reflect.TypeOf(vt)
if ty.Kind() == reflect.Pointer {
rp := reflect.ValueOf(v) // rp is the reflected pointer value of v
if rp.IsNil() {
// v is a typed nil pointer
return e.emitNil()
} else {
// v is a non-nil pointer; dereference it and emit that
return e.Emit(rp.Elem().Interface())
}
} else if ty.Kind() == reflect.Slice {
// Support non-`any` slices via reflection
rv := reflect.ValueOf(v)
err = e.emitArrayBegin(0)
if err != nil {
return
}
notFirst := false
for i := 0; i < rv.Len(); i++ {
av := rv.Index(i).Interface()
if notFirst {
err = e.emitArrayNext()
if err != nil {
return
}
}
notFirst = true
err = e.Emit(av)
if err != nil {
return
}
}
return e.emitArrayEnd()
} else if ty.Kind() == reflect.Map {
// Support non-`any`-valued maps via reflection, as long as the key
// type is exactly `string`
if ty.Key() != reflect.TypeOf("") {
return fmt.Errorf("simple json: cannot emit unsupported type %T", v)
}

rv := reflect.ValueOf(v)
err = e.emitMapBegin(0)
if err != nil {
return
}
notFirst := false
iter := rv.MapRange()
for iter.Next() {
key := iter.Key().String()
value := iter.Value().Interface()
if notFirst {
err = e.emitMapNext()
if err != nil {
return
}
}
notFirst = true
err = e.emitString(key)
if err != nil {
return
}
err = e.emitMapValue()
if err != nil {
return
}
err = e.Emit(value)
if err != nil {
return
}
}
return e.emitMapEnd()
}
}
return fmt.Errorf("simple json: cannot emit unsupported type %T", v)
Expand Down
17 changes: 17 additions & 0 deletions json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,20 @@ func TestWhitespaceSkipping(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, []any{true, false}, val)
}

func TestReflectedEmit(t *testing.T) {
res, err := MarshalToString(map[string]map[string]int64{"foo": {"bar": 1}})
require.NoError(t, err)
assert.Equal(t, `{"foo":{"bar":1}}`, res)

False, True := false, true
res, err = MarshalToString([]*bool{nil, &False, &True})
require.NoError(t, err)
assert.Equal(t, `[null,false,true]`, res)

// non-`string` keys are forbidden
_, err = MarshalToString(map[int]int{1: 2})
assert.ErrorContains(t, err, "simple json: cannot emit unsupported type map[int]int")
_, err = MarshalToString([]map[string]map[int]int{{"foo": {1: 2}}})
assert.ErrorContains(t, err, "simple json: cannot emit unsupported type map[int]int")
}

0 comments on commit 2a76dca

Please sign in to comment.