diff --git a/jsonpointer/resolve.go b/jsonpointer/resolve.go index 1a392a9..dcf7958 100644 --- a/jsonpointer/resolve.go +++ b/jsonpointer/resolve.go @@ -25,6 +25,14 @@ func Resolve(base any, pointer Pointer) (any, error) { } func findByLabel(base reflect.Value, jsonLabel string) (reflect.Value, bool) { + if resolver, ok := base.Interface().(LabelResolver); ok { + value, ok := resolver.ResolveJsonLabel(jsonLabel) + if ok { + return reflect.ValueOf(value).Elem(), true + } + return reflect.Value{}, false + } + for base.Kind() == reflect.Ptr || base.Kind() == reflect.Interface { if base.IsNil() { return reflect.Value{}, false @@ -75,3 +83,10 @@ func findStructFieldByLabel(base reflect.Value, label string) (reflect.Value, bo } return reflect.Value{}, false } + +type LabelResolver interface { + // ResolveJsonLabel resolves the given JSON label to a value. + // If the label is not found, (nil, false) is returned. + // The returned value must be a pointer to the field. + ResolveJsonLabel(label string) (any, bool) +} diff --git a/thorlog/v3/kvlist.go b/thorlog/v3/kvlist.go index b5835ac..ac138a5 100644 --- a/thorlog/v3/kvlist.go +++ b/thorlog/v3/kvlist.go @@ -14,14 +14,12 @@ type KeyValue struct { Value string } -type KeyValueList struct { - KvList []KeyValue -} +type KeyValueList []KeyValue func (d KeyValueList) MarshalJSON() ([]byte, error) { var builder strings.Builder builder.WriteString("{") - for i, kv := range d.KvList { + for i, kv := range d { if err := json.NewEncoder(&builder).Encode(kv.Key); err != nil { return nil, err } @@ -29,7 +27,7 @@ func (d KeyValueList) MarshalJSON() ([]byte, error) { if err := json.NewEncoder(&builder).Encode(kv.Value); err != nil { return nil, err } - if i < len(d.KvList)-1 { + if i < len(d)-1 { builder.WriteString(", ") } } @@ -48,16 +46,22 @@ func (d *KeyValueList) UnmarshalJSON(data []byte) error { } var kvList []KeyValue for decoder.More() { - var key string - err = decoder.Decode(&key) + keyToken, err := decoder.Token() if err != nil { return err } - var value string - err = decoder.Decode(&value) + key, isString := keyToken.(string) + if !isString { + return errors.New("expected string key") + } + valueToken, err := decoder.Token() if err != nil { return err } + value, isString := valueToken.(string) + if !isString { + return errors.New("expected string value") + } kvList = append(kvList, KeyValue{Key: key, Value: value}) } token, err = decoder.Token() @@ -67,18 +71,27 @@ func (d *KeyValueList) UnmarshalJSON(data []byte) error { if delim, isDelim := token.(json.Delim); !isDelim || delim != '}' { return errors.New("expected '}'") } - d.KvList = kvList + *d = kvList return nil } +func (d KeyValueList) ResolveJsonLabel(label string) (any, bool) { + for i := range d { + if d[i].Key == label { + return &d[i].Value, true + } + } + return nil, false +} + func (d KeyValueList) RelativeJsonPointer(pointee any) jsonpointer.Pointer { stringPointer, isStringPointer := pointee.(*string) if !isStringPointer { return nil } - for i := range d.KvList { - if &d.KvList[i].Value == stringPointer { - return jsonpointer.New(d.KvList[i].Key) + for i := range d { + if &d[i].Value == stringPointer { + return jsonpointer.New(d[i].Key) } } return nil @@ -89,18 +102,18 @@ func (d KeyValueList) RelativeTextPointer(pointee any) (string, bool) { if !isStringPointer { return "", false } - for i := range d.KvList { - if &d.KvList[i].Value == stringPointer { - return d.KvList[i].Key, true + for i := range d { + if &d[i].Value == stringPointer { + return d[i].Key, true } } return "", false } func (d KeyValueList) Find(key string) *string { - for i := range d.KvList { - if d.KvList[i].Key == key { - return &d.KvList[i].Value + for i := range d { + if d[i].Key == key { + return &d[i].Value } } return nil @@ -108,11 +121,11 @@ func (d KeyValueList) Find(key string) *string { func (d KeyValueList) String() string { var dataBuilder strings.Builder - for i, kv := range d.KvList { + for i, kv := range d { dataBuilder.WriteString(kv.Key) dataBuilder.WriteString(": ") dataBuilder.WriteString(kv.Value) - if i < len(d.KvList)-1 { + if i < len(d)-1 { dataBuilder.WriteString(" ") } } diff --git a/thorlog/v3/kvlist_test.go b/thorlog/v3/kvlist_test.go new file mode 100644 index 0000000..a437f8a --- /dev/null +++ b/thorlog/v3/kvlist_test.go @@ -0,0 +1,47 @@ +package thorlog + +import ( + "encoding/json" + "testing" + + "github.com/NextronSystems/jsonlog" + "github.com/NextronSystems/jsonlog/jsonpointer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestKeyValueList_MarshalJSON(t *testing.T) { + var kvList = KeyValueList{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + } + data, err := json.Marshal(kvList) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if string(data) != `{"key1":"value1","key2":"value2"}` { + t.Errorf("unexpected JSON: %s", data) + } + var unmarshaled KeyValueList + err = json.Unmarshal(data, &unmarshaled) + if err != nil { + t.Fatalf("unexpected error during unmarshal: %v", err) + } + if unmarshaled[0].Key != "key1" || unmarshaled[0].Value != "value1" || unmarshaled[1].Key != "key2" || unmarshaled[1].Value != "value2" { + t.Errorf("unexpected unmarshaled data: %+v", unmarshaled) + } +} + +func TestKeyValueList_JsonPointers(t *testing.T) { + var kvList = KeyValueList{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + } + reference := jsonlog.Reference{Base: &kvList, PointedField: &kvList[1].Value} + pointer := reference.ToJsonPointer() + assert.Equal(t, "/key2", pointer.String()) + + reverse, err := jsonpointer.Resolve(&kvList, pointer) + require.NoError(t, err) + assert.Equal(t, &kvList[1].Value, reverse) +}