diff options
| -rw-r--r-- | ccl.go | 27 | ||||
| -rw-r--r-- | ccl_test.go | 19 |
2 files changed, 37 insertions, 9 deletions
@@ -592,6 +592,16 @@ func (p *parser) parse() (map[string]any, error) { } } +func setPtr(val reflect.Value) reflect.Value { + if val.Kind() != reflect.Pointer { + return val + } + if val.IsNil() { + val.Set(reflect.New(val.Type().Elem())) + } + return val.Elem() +} + func intLimits(kind reflect.Kind) (min, max uint64, ok bool) { switch kind { case reflect.Int: @@ -622,6 +632,7 @@ func intLimits(kind reflect.Kind) (min, max uint64, ok bool) { func unpackVal(fieldVal reflect.Value, fieldMap map[structField]int, val any, field string) error { switch val := val.(type) { case bool: + fieldVal := setPtr(fieldVal) switch fieldVal.Kind() { case reflect.Bool: fieldVal.SetBool(val) @@ -641,6 +652,7 @@ func unpackVal(fieldVal reflect.Value, fieldMap map[structField]int, val any, fi return fmt.Errorf("field %q should have type bool", field) } case *integer: + fieldVal := setPtr(fieldVal) switch fieldVal.Kind() { case reflect.Float32, reflect.Float64: fieldVal.SetFloat(float64(val.sgn) * float64(val.n)) @@ -659,6 +671,7 @@ func unpackVal(fieldVal reflect.Value, fieldMap map[structField]int, val any, fi fieldVal.SetInt(int64(val.sgn) * int64(val.n)) } case float64: + fieldVal := setPtr(fieldVal) switch fieldVal.Kind() { case reflect.Float32, reflect.Float64: fieldVal.SetFloat(float64(val)) @@ -675,10 +688,11 @@ func unpackVal(fieldVal reflect.Value, fieldMap map[structField]int, val any, fi if unmarshaler, ok := fieldVal.Addr().Interface().(encoding.TextUnmarshaler); ok { return unmarshaler.UnmarshalText([]byte(val)) } + fieldVal := setPtr(fieldVal) switch { case fieldVal.Kind() == reflect.String: fieldVal.SetString(val) - case fieldVal.Kind() == reflect.Slice && fieldVal.Type().Elem() == reflect.TypeFor[byte](): + case fieldVal.Type() == reflect.TypeFor[[]byte](): b, err := base64.StdEncoding.DecodeString(val) if err != nil { return fmt.Errorf("field %q: bad base64", field) @@ -688,15 +702,10 @@ func unpackVal(fieldVal reflect.Value, fieldMap map[structField]int, val any, fi return fmt.Errorf("field %q should have type string (got %s)", field, fieldVal.Type()) } case map[string]any: - if !(fieldVal.Kind() == reflect.Struct || fieldVal.Kind() == reflect.Pointer && fieldVal.Type().Elem().Kind() == reflect.Struct) { + fieldVal := setPtr(fieldVal) + if fieldVal.Kind() != reflect.Struct { return fmt.Errorf("field %q should have type struct (got %s)", field, fieldVal.Type()) } - if fieldVal.Kind() == reflect.Pointer { - if fieldVal.IsNil() { - fieldVal.Set(reflect.New(fieldVal.Type().Elem())) - } - fieldVal = fieldVal.Elem() - } if err := unpackStruct(fieldVal, fieldMap, val); err != nil { return err } @@ -715,7 +724,7 @@ func unpackStruct(out reflect.Value, fieldMap map[structField]int, msg map[strin return fmt.Errorf("no field named %q", field) } fieldVal := out.Field(fieldIdx) - if fieldVal.Kind() == reflect.Slice && fieldVal.Type().Elem() != reflect.TypeFor[byte]() { + if fieldVal.Kind() == reflect.Slice && fieldVal.Type() != reflect.TypeFor[[]byte]() { var vals []any if l, ok := val.([]any); ok { vals = l diff --git a/ccl_test.go b/ccl_test.go index 938af09..ab4833f 100644 --- a/ccl_test.go +++ b/ccl_test.go @@ -11,6 +11,7 @@ import ( func TestUnmarshal(t *testing.T) { t.Parallel() + type byteSliceWrapper []byte type nestedMessage struct { Field int64 `ccl:"field"` } @@ -34,8 +35,11 @@ func TestUnmarshal(t *testing.T) { Repeated []int64 `ccl:"repeated"` RepeatedMessage []*nestedMessage `ccl:"repeated_message"` Bytes []byte `ccl:"bytes"` + BytesWrapper byteSliceWrapper `ccl:"bytes_wrapper"` Time time.Time `ccl:"time"` TimePointer *time.Time `ccl:"time_pointer"` + IntPointer *int `ccl:"int_pointer"` + RepeatedPointer []*int `ccl:"repeated_pointer"` Ignore map[int]int `ccl:"-,"` // unlike JSON this also means ignore unexported int64 @@ -330,6 +334,10 @@ from string'`, msg: `bytes:"dGVzdA=="`, want: message{Bytes: []byte("test")}, }, { + desc: "NotBase64", + msg: `bytes_wrapper: [1, 2, 3]`, + want: message{BytesWrapper: byteSliceWrapper{1, 2, 3}}, + }, { desc: "TextUnmarshaler", msg: `time:"2025-10-28T07:41:47Z"`, want: message{Time: time.Date(2025, time.October, 28, 7, 41, 47, 0, time.UTC)}, @@ -337,6 +345,14 @@ from string'`, desc: "TextUnmarshalerPointer", msg: `time_pointer:"2025-10-28T07:41:47Z"`, want: message{TimePointer: &[]time.Time{time.Date(2025, time.October, 28, 7, 41, 47, 0, time.UTC)}[0]}, + }, { + desc: "IntPointer", + msg: `int_pointer: 5`, + want: message{IntPointer: &[]int{5}[0]}, + }, { + desc: "RepeatedPointer", + msg: `repeated_pointer: [1, 2, 3]`, + want: message{RepeatedPointer: []*int{&[]int{1}[0], &[]int{2}[0], &[]int{3}[0]}}, }} { t.Run(tc.desc, func(t *testing.T) { t.Parallel() @@ -455,6 +471,9 @@ func TestUnmarshal_Invalid(t *testing.T) { desc: "Base64", msg: `bytes:"dGVzdAo"`, }, { + desc: "NotBase64", + msg: `bytes:[1,2,3]`, + }, { desc: "BadField", msg: `asdfasdfasdf:"asdf"`, }, { |
