Skip to content

Commit

Permalink
Add support for raw map[string]any type to riverjson encoding.
Browse files Browse the repository at this point in the history
  • Loading branch information
wildum committed Jan 22, 2024
1 parent 0f30d3c commit 588c20a
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 17 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ internal API changes are not present.
Main (unreleased)
-----------------

### Features

- Add support for raw map[string]any type to riverjson encoding. (@wildum)

v0.3.0 (2023-10-26)
-------------------

Expand Down
54 changes: 37 additions & 17 deletions encoding/riverjson/riverjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
var goRiverDefaulter = reflect.TypeOf((*value.Defaulter)(nil)).Elem()

// MarshalBody marshals the provided Go value to a JSON representation of
// River. MarshalBody panics if not given a struct with River tags.
// River. MarshalBody panics if not given a struct with River tags or a map[string]any.
func MarshalBody(val interface{}) ([]byte, error) {
rv := reflect.ValueOf(val)
return json.Marshal(encodeStructAsBody(rv))
Expand All @@ -33,30 +33,50 @@ func encodeStructAsBody(rv reflect.Value) jsonBody {

if rv.Kind() == reflect.Invalid {
return []jsonStatement{}
} else if rv.Kind() != reflect.Struct {
panic(fmt.Sprintf("river/encoding/riverjson: can only encode struct values to bodies, got %s", rv.Kind()))
}

fields := rivertags.Get(rv.Type())
defaults := reflect.New(rv.Type()).Elem()
if defaults.CanAddr() && defaults.Addr().Type().Implements(goRiverDefaulter) {
defaults.Addr().Interface().(value.Defaulter).SetToDefault()
}

body := []jsonStatement{}

for _, field := range fields {
fieldVal := reflectutil.Get(rv, field)
fieldValDefault := reflectutil.Get(defaults, field)
switch rv.Kind() {
case reflect.Struct:
fields := rivertags.Get(rv.Type())
defaults := reflect.New(rv.Type()).Elem()
if defaults.CanAddr() && defaults.Addr().Type().Implements(goRiverDefaulter) {
defaults.Addr().Interface().(value.Defaulter).SetToDefault()
}

var isEqual = fieldVal.Comparable() && fieldVal.Equal(fieldValDefault)
var isZero = fieldValDefault.IsZero() && fieldVal.IsZero()
for _, field := range fields {
fieldVal := reflectutil.Get(rv, field)
fieldValDefault := reflectutil.Get(defaults, field)

if field.IsOptional() && (isEqual || isZero) {
continue
isEqual := fieldVal.Comparable() && fieldVal.Equal(fieldValDefault)
isZero := fieldValDefault.IsZero() && fieldVal.IsZero()

if field.IsOptional() && (isEqual || isZero) {
continue
}

body = append(body, encodeFieldAsStatements(nil, field, fieldVal)...)
}

body = append(body, encodeFieldAsStatements(nil, field, fieldVal)...)
case reflect.Map:
if rv.Type().Key().Kind() != reflect.String {
panic("river/encoding/riverjson: unsupported map type; expected map[string]T, got " + rv.Type().String())
}

iter := rv.MapRange()
for iter.Next() {
mapKey, mapValue := iter.Key(), iter.Value()

body = append(body, jsonAttr{
Name: mapKey.String(),
Type: "attr",
Value: buildJSONValue(value.FromRaw(mapValue)),
})
}

default:
panic(fmt.Sprintf("river/encoding/riverjson: can only encode struct or map values to bodies, got %s", rv.Kind()))
}

return body
Expand Down
19 changes: 19 additions & 0 deletions encoding/riverjson/riverjson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,22 @@ func TestMapBlocks(t *testing.T) {
require.NoError(t, err)
require.JSONEq(t, expect, string(bb))
}

func TestRawMap(t *testing.T) {
val := map[string]any{"field": "value", "anotherField": 3}

expect := `[{
"name": "field",
"type": "attr",
"value": { "type": "string", "value": "value" }
},
{
"name": "anotherField",
"type": "attr",
"value": { "type": "number", "value": 3 }
}]`

bb, err := riverjson.MarshalBody(val)
require.NoError(t, err)
require.JSONEq(t, expect, string(bb))
}

0 comments on commit 588c20a

Please sign in to comment.