From b1f97d8f1ce1e769990fc68049e6cb85547eca2a Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 10 Dec 2019 11:35:03 -0600 Subject: [PATCH 1/2] Add reflection encoder for lists --- map.go | 29 +++++++++++++++++++++++------ map_test.go | 2 +- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/map.go b/map.go index 2a50c37..034a61b 100644 --- a/map.go +++ b/map.go @@ -13,6 +13,11 @@ import ( // Map represents an ordered map of fields. type Map []Field +// SlogValue implements Value. +func (m Map) SlogValue() interface{} { + return ForceJSON(m) +} + var _ json.Marshaler = Map(nil) // MarshalJSON implements json.Marshaler. @@ -55,14 +60,14 @@ func (v jsonVal) MarshalJSON() ([]byte, error) { return json.Marshal(v.v) } -func marshalArray(a []interface{}) []byte { +func marshalList(rv reflect.Value) []byte { b := &bytes.Buffer{} b.WriteByte('[') - for i, v := range a { + for i := 0; i < rv.Len(); i++ { b.WriteByte('\n') - b.Write(encode(v)) + b.Write(encode(rv.Index(i).Interface())) - if i < len(a)-1 { + if i < rv.Len()-1 { b.WriteByte(',') } } @@ -75,13 +80,25 @@ func encode(v interface{}) []byte { switch v := v.(type) { case Value: return encode(v.SlogValue()) - case []interface{}: - return marshalArray(v) case xerrors.Formatter: return encode(errorChain(v)) case error, fmt.Stringer: return encode(fmt.Sprint(v)) default: + rv := reflect.Indirect(reflect.ValueOf(v)) + if rv.IsValid() { + switch rv.Type().Kind() { + case reflect.Slice: + if rv.IsNil() { + b, _ := json.Marshal(nil) + return b + } + fallthrough + case reflect.Array: + return marshalList(rv) + } + } + b, err := json.Marshal(v) if err != nil { return encode(M( diff --git a/map_test.go b/map_test.go index 26907d2..bea594a 100644 --- a/map_test.go +++ b/map_test.go @@ -93,7 +93,7 @@ func TestMap(t *testing.T) { { "msg": "failed to marshal to JSON", "fun": "cdr.dev/slog.encode", - "loc": "`+mapTestFile+`:88" + "loc": "`+mapTestFile+`:105" }, "json: unsupported type: func(*testing.T, string) string" ], From 1dec25abb04e89bf3a0b67aed0c116f2bbb3ce09 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 10 Dec 2019 11:44:50 -0600 Subject: [PATCH 2/2] Document encoding --- map.go | 18 ++++++++++++++---- map_test.go | 12 +++++++++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/map.go b/map.go index 034a61b..435b406 100644 --- a/map.go +++ b/map.go @@ -23,8 +23,19 @@ var _ json.Marshaler = Map(nil) // MarshalJSON implements json.Marshaler. // // It is guaranteed to return a nil error. -// Any error marshalling a field will -// become the field's value. +// Any error marshalling a field will become the field's value. +// +// Every field value is encoded with the following process: +// +// 1. slog.Value is handled to allow any type to replace its representation for logging. +// +// 2. xerrors.Formatter is handled. +// +// 3. error and fmt.Stringer are handled. +// +// 4. slices and arrays are handled to go through the encode function for every value. +// +// 5. json.Marshal is invoked as the default case. func (m Map) MarshalJSON() ([]byte, error) { b := &bytes.Buffer{} b.WriteByte('{') @@ -90,8 +101,7 @@ func encode(v interface{}) []byte { switch rv.Type().Kind() { case reflect.Slice: if rv.IsNil() { - b, _ := json.Marshal(nil) - return b + break } fallthrough case reflect.Array: diff --git a/map_test.go b/map_test.go index bea594a..150b354 100644 --- a/map_test.go +++ b/map_test.go @@ -93,7 +93,7 @@ func TestMap(t *testing.T) { { "msg": "failed to marshal to JSON", "fun": "cdr.dev/slog.encode", - "loc": "`+mapTestFile+`:105" + "loc": "`+mapTestFile+`:115" }, "json: unsupported type: func(*testing.T, string) string" ], @@ -164,6 +164,16 @@ func TestMap(t *testing.T) { "error": "xdxd" }`) }) + + t.Run("nilSlice", func(t *testing.T) { + t.Parallel() + + test(t, slog.M( + slog.F("slice", []string(nil)), + ), `{ + "slice": null + }`) + }) } type meow struct {