From 8e3499ca83930240974e0baacdbddb6f41a6b067 Mon Sep 17 00:00:00 2001 From: Blake Pettersson Date: Fri, 9 Feb 2018 18:01:31 +0100 Subject: [PATCH] Improved slice unmarshalling support * All integer slice types are supported JSON number arrays can be unmarshaled as `int`, `int8`, `int16`, `int32` and `int64` slices, as well as `uint`, `uint16`, `uint32` and `uint64` slices, as well as slices that are pointers to the aforementioned types. In the case of `uint8` slices, the assumption is that it is unmarshaled from a base64 encoded string, since that is how a `[]uint8` is marshaled with `json.Marshal`. * JSON arrays can also be unmarshaled as `float32` and `float64` slices, as well as the pointer variants of those types. * `time.Time` slices are also supported, both values and pointers. --- models_test.go | 38 ++++ request.go | 505 +++++++++++++++++++++++++-------------------- request_test.go | 539 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 863 insertions(+), 219 deletions(-) diff --git a/models_test.go b/models_test.go index a53dd614..d378b47d 100644 --- a/models_test.go +++ b/models_test.go @@ -31,6 +31,44 @@ type Timestamp struct { Next *time.Time `jsonapi:"attr,next,iso8601"` } +type Timestamps struct { + ID int `jsonapi:"primary,timestamp-arrays"` + Time []time.Time `jsonapi:"attr,timestamps,iso8601"` + Next []*time.Time `jsonapi:"attr,next,iso8601"` +} + +type NumberArrays struct { + ID int `jsonapi:"primary,number-arrays"` + Ints []int `jsonapi:"attr,ints"` + Int8s []int8 `jsonapi:"attr,8-ints"` + Int16s []int16 `jsonapi:"attr,16-ints"` + Int32s []int32 `jsonapi:"attr,32-ints"` + Int64s []int64 `jsonapi:"attr,64-ints"` + UInts []uint `jsonapi:"attr,uints"` + UInt8s []uint8 `jsonapi:"attr,uint8s"` + UInt16s []uint16 `jsonapi:"attr,uint16s"` + UInt32s []uint32 `jsonapi:"attr,uint32s"` + UInt64s []uint64 `jsonapi:"attr,uint64s"` + Floats []float32 `jsonapi:"attr,floats"` + Doubles []float64 `jsonapi:"attr,doubles"` +} + +type NumberPtrArrays struct { + ID int `jsonapi:"primary,number-ptr-arrays"` + Ints []*int `jsonapi:"attr,ints"` + Int8s []*int8 `jsonapi:"attr,8-ints"` + Int16s []*int16 `jsonapi:"attr,16-ints"` + Int32s []*int32 `jsonapi:"attr,32-ints"` + Int64s []*int64 `jsonapi:"attr,64-ints"` + UInts []*uint `jsonapi:"attr,uints"` + UInt8s []*uint8 `jsonapi:"attr,uint8s"` + UInt16s []*uint16 `jsonapi:"attr,uint16s"` + UInt32s []*uint32 `jsonapi:"attr,uint32s"` + UInt64s []*uint64 `jsonapi:"attr,uint64s"` + Floats []*float32 `jsonapi:"attr,floats"` + Doubles []*float64 `jsonapi:"attr,doubles"` +} + type Car struct { ID *string `jsonapi:"primary,cars"` Make *string `jsonapi:"attr,make,omitempty"` diff --git a/request.go b/request.go index fe29706a..59cb91d5 100644 --- a/request.go +++ b/request.go @@ -2,10 +2,12 @@ package jsonapi import ( "bytes" + "encoding/base64" "encoding/json" "errors" "fmt" "io" + "io/ioutil" "reflect" "strconv" "strings" @@ -27,11 +29,15 @@ var ( // (numeric) but the Struct field was a non numeric type (i.e. not int, uint, // float, etc) ErrUnknownFieldNumberType = errors.New("The struct field was not of a known number type") + // ErrInvalidBase64Str is returned when an input string is invalid base64 + ErrInvalidBase64Str = errors.New("The input could not be decoded as a base64 string") // ErrUnsupportedPtrType is returned when the Struct field was a pointer but // the JSON value was of a different type ErrUnsupportedPtrType = errors.New("Pointer type in struct is not supported") // ErrInvalidType is returned when the given type is incompatible with the expected type. ErrInvalidType = errors.New("Invalid type provided") // I wish we used punctuation. + // ErrUnsupportedSliceType is returned when the given slice type cannot be unmarshaled. + ErrUnsupportedSliceType = errors.New("Slice type is not supported") ) // UnmarshalPayload converts an io into a struct instance using jsonapi tags on @@ -194,48 +200,13 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) break } - // Convert the numeric float to one of the supported ID numeric types - // (int[8,16,32,64] or uint[8,16,32,64]) - var idValue reflect.Value - switch kind { - case reflect.Int: - n := int(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Int8: - n := int8(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Int16: - n := int16(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Int32: - n := int32(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Int64: - n := int64(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint: - n := uint(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint8: - n := uint8(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint16: - n := uint16(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint32: - n := uint32(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint64: - n := uint64(floatValue) - idValue = reflect.ValueOf(&n) - default: + err = unmarshalNumber(floatValue, fieldValue, fieldValue.Type()) + if err != nil { // We had a JSON float (numeric), but our field was not one of the // allowed numeric types er = ErrBadJSONAPIID break } - - assign(fieldValue, idValue) } else if annotation == annotationClientID { if data.ClientID == "" { continue @@ -267,189 +238,11 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) v := reflect.ValueOf(val) - // Handle field of type time.Time - if fieldValue.Type() == reflect.TypeOf(time.Time{}) { - if iso8601 { - var tm string - if v.Kind() == reflect.String { - tm = v.Interface().(string) - } else { - er = ErrInvalidISO8601 - break - } - - t, err := time.Parse(iso8601TimeFormat, tm) - if err != nil { - er = ErrInvalidISO8601 - break - } - - fieldValue.Set(reflect.ValueOf(t)) - - continue - } - - var at int64 - - if v.Kind() == reflect.Float64 { - at = int64(v.Interface().(float64)) - } else if v.Kind() == reflect.Int { - at = v.Int() - } else { - return ErrInvalidTime - } - - t := time.Unix(at, 0) - - fieldValue.Set(reflect.ValueOf(t)) - - continue - } - - if fieldValue.Type() == reflect.TypeOf([]string{}) { - values := make([]string, v.Len()) - for i := 0; i < v.Len(); i++ { - values[i] = v.Index(i).Interface().(string) - } - - fieldValue.Set(reflect.ValueOf(values)) - - continue - } - - if fieldValue.Type() == reflect.TypeOf(new(time.Time)) { - if iso8601 { - var tm string - if v.Kind() == reflect.String { - tm = v.Interface().(string) - } else { - er = ErrInvalidISO8601 - break - } - - v, err := time.Parse(iso8601TimeFormat, tm) - if err != nil { - er = ErrInvalidISO8601 - break - } - - t := &v - - fieldValue.Set(reflect.ValueOf(t)) - - continue - } - - var at int64 - - if v.Kind() == reflect.Float64 { - at = int64(v.Interface().(float64)) - } else if v.Kind() == reflect.Int { - at = v.Int() - } else { - return ErrInvalidTime - } - - v := time.Unix(at, 0) - t := &v - - fieldValue.Set(reflect.ValueOf(t)) - - continue - } - - // JSON value was a float (numeric) - if v.Kind() == reflect.Float64 { - floatValue := v.Interface().(float64) - - // The field may or may not be a pointer to a numeric; the kind var - // will not contain a pointer type - var kind reflect.Kind - if fieldValue.Kind() == reflect.Ptr { - kind = fieldType.Type.Elem().Kind() - } else { - kind = fieldType.Type.Kind() - } - - var numericValue reflect.Value - - switch kind { - case reflect.Int: - n := int(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Int8: - n := int8(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Int16: - n := int16(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Int32: - n := int32(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Int64: - n := int64(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint: - n := uint(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint8: - n := uint8(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint16: - n := uint16(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint32: - n := uint32(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint64: - n := uint64(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Float32: - n := float32(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Float64: - n := floatValue - numericValue = reflect.ValueOf(&n) - default: - return ErrUnknownFieldNumberType - } - - assign(fieldValue, numericValue) - continue - } - - // Field was a Pointer type - if fieldValue.Kind() == reflect.Ptr { - var concreteVal reflect.Value - - switch cVal := val.(type) { - case string: - concreteVal = reflect.ValueOf(&cVal) - case bool: - concreteVal = reflect.ValueOf(&cVal) - case complex64: - concreteVal = reflect.ValueOf(&cVal) - case complex128: - concreteVal = reflect.ValueOf(&cVal) - case uintptr: - concreteVal = reflect.ValueOf(&cVal) - default: - return ErrUnsupportedPtrType - } - - if fieldValue.Type() != concreteVal.Type() { - return ErrUnsupportedPtrType - } - - fieldValue.Set(concreteVal) - continue - } - - // As a final catch-all, ensure types line up to avoid a runtime panic. - if fieldValue.Kind() != v.Kind() { - return ErrInvalidType + err := unmarshalValue(fieldValue, v, fieldType.Type, iso8601) + if err != nil { + er = err + break } - fieldValue.Set(reflect.ValueOf(val)) } else if annotation == annotationRelation { isSlice := fieldValue.Type().Kind() == reflect.Slice @@ -529,6 +322,279 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) return er } +func unmarshalValue(fieldValue, v reflect.Value, fieldType reflect.Type, iso8601 bool) error { + // Handle slices + if fieldValue.Kind() == reflect.Slice { + t := fieldValue.Type() + sliceType := t.Elem() + + if sliceType.Kind() == reflect.Ptr { + // Then dereference it + sliceType = sliceType.Elem() + } + + // []uint8 will be decoded as a base64-encoded string + if sliceType.Kind() == reflect.Uint8 { + decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(v.Interface().(string))) + body, e := ioutil.ReadAll(decoder) + if e != nil { + return ErrInvalidBase64Str + } + + if t.Elem().Kind() == reflect.Ptr { + var uint8PtrSlice []*uint8 + + for i := range body { + uint8PtrSlice = append(uint8PtrSlice, &body[i]) + } + + fieldValue.Set(reflect.ValueOf(uint8PtrSlice)) + } else { + fieldValue.Set(reflect.ValueOf(body)) + } + + return nil + } + + values := reflect.MakeSlice(reflect.SliceOf(t.Elem()), v.Len(), v.Len()) + + for i := 0; i < v.Len(); i++ { + val := v.Index(i).Interface() + switch fieldValue.Type().Elem() { + + // Try to unmarshal time types + case reflect.TypeOf(time.Time{}): + t := time.Time{} + value := reflect.ValueOf(&t) + e := unmarshalTime(reflect.ValueOf(val.(string)), value.Elem(), iso8601) + if e != nil { + return e + } + + values.Index(i).Set(reflect.ValueOf(t)) + continue + case reflect.TypeOf(new(time.Time)): + t := new(time.Time) + value := reflect.ValueOf(&t) + e := unmarshalTimePtr(reflect.ValueOf(val.(string)), value.Elem(), iso8601) + if e != nil { + return e + } + + values.Index(i).Set(reflect.ValueOf(t)) + continue + } + + switch sliceType.Kind() { + // If the slice type is a string, unmarshal it + case reflect.String: + values.Index(i).Set(reflect.ValueOf(val)) + // Attempt to unmarshal number types + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, + reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: + e := unmarshalNumber(val, values.Index(i), fieldValue.Type().Elem()) + if e != nil { + return e + } + // No other slice types are currently supported + default: + return ErrUnsupportedSliceType + } + } + + fieldValue.Set(values) + + return nil + } + + // Handle field of type time.Time + if fieldValue.Type() == reflect.TypeOf(time.Time{}) { + return unmarshalTime(v, fieldValue, iso8601) + } + + // Handle field of type *time.Time + if fieldValue.Type() == reflect.TypeOf(new(time.Time)) { + return unmarshalTimePtr(v, fieldValue, iso8601) + } + + // JSON value was a float (numeric) + if v.Kind() == reflect.Float64 { + return unmarshalNumber(v.Interface(), fieldValue, fieldType) + } + + // Field was a Pointer type + if fieldValue.Kind() == reflect.Ptr { + return unmarshalPtr(v, fieldValue) + } + + // As a final catch-all, ensure types line up to avoid a runtime panic. + if fieldValue.Kind() != v.Kind() { + return ErrInvalidType + } + + fieldValue.Set(reflect.ValueOf(v.Interface())) + return nil +} + +func unmarshalTime(v reflect.Value, fieldValue reflect.Value, iso8601 bool) error { + if iso8601 { + var tm string + if v.Kind() == reflect.String { + tm = v.Interface().(string) + } else { + return ErrInvalidISO8601 + } + + t, err := time.Parse(iso8601TimeFormat, tm) + if err != nil { + return ErrInvalidISO8601 + } + + fieldValue.Set(reflect.ValueOf(t)) + return nil + } + + var at int64 + + if v.Kind() == reflect.Float64 { + at = int64(v.Interface().(float64)) + } else if v.Kind() == reflect.Int { + at = v.Int() + } else { + return ErrInvalidTime + } + + t := time.Unix(at, 0) + + fieldValue.Set(reflect.ValueOf(t)) + + return nil +} + +func unmarshalTimePtr(v, fieldValue reflect.Value, iso8601 bool) error { + if iso8601 { + var tm string + if v.Kind() == reflect.String { + tm = v.Interface().(string) + } else { + return ErrInvalidISO8601 + } + + v, err := time.Parse(iso8601TimeFormat, tm) + if err != nil { + return ErrInvalidISO8601 + } + + t := &v + + fieldValue.Set(reflect.ValueOf(t)) + + return nil + } + + var at int64 + + if v.Kind() == reflect.Float64 { + at = int64(v.Interface().(float64)) + } else if v.Kind() == reflect.Int { + at = v.Int() + } else { + return ErrInvalidTime + } + + unix := time.Unix(at, 0) + t := &unix + + fieldValue.Set(reflect.ValueOf(t)) + + return nil +} + +func unmarshalNumber(v interface{}, fieldValue reflect.Value, fieldType reflect.Type) error { + floatValue := v.(float64) + + // The field may or may not be a pointer to a numeric; the kind var + // will not contain a pointer type + var kind reflect.Kind + if fieldValue.Kind() == reflect.Ptr { + kind = fieldType.Elem().Kind() + } else { + kind = fieldType.Kind() + } + + var numericValue reflect.Value + + switch kind { + case reflect.Int: + n := int(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Int8: + n := int8(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Int16: + n := int16(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Int32: + n := int32(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Int64: + n := int64(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint: + n := uint(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint8: + n := uint8(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint16: + n := uint16(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint32: + n := uint32(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint64: + n := uint64(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Float32: + n := float32(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Float64: + n := floatValue + numericValue = reflect.ValueOf(&n) + default: + return ErrUnknownFieldNumberType + } + + assign(fieldValue, numericValue) + return nil +} + +func unmarshalPtr(v, fieldValue reflect.Value) error { + var concreteVal reflect.Value + + switch cVal := v.Interface().(type) { + case string: + concreteVal = reflect.ValueOf(&cVal) + case bool: + concreteVal = reflect.ValueOf(&cVal) + case complex64: + concreteVal = reflect.ValueOf(&cVal) + case complex128: + concreteVal = reflect.ValueOf(&cVal) + case uintptr: + concreteVal = reflect.ValueOf(&cVal) + default: + return ErrUnsupportedPtrType + } + + if fieldValue.Type() != concreteVal.Type() { + return ErrUnsupportedPtrType + } + + fieldValue.Set(concreteVal) + return nil +} + func fullNode(n *Node, included *map[string]*Node) *Node { includedKey := fmt.Sprintf("%s,%s", n.Type, n.ID) @@ -542,6 +608,7 @@ func fullNode(n *Node, included *map[string]*Node) *Node { // assign will take the value specified and assign it to the field; if // field is expecting a ptr assign will assign a ptr. func assign(field, value reflect.Value) { + if field.Kind() == reflect.Ptr { field.Set(value) } else { diff --git a/request_test.go b/request_test.go index 22064495..d655c22a 100644 --- a/request_test.go +++ b/request_test.go @@ -264,6 +264,545 @@ func TestUnmarshalSetsAttrs(t *testing.T) { } } +func TestUnmarshalParsesIntArray(t *testing.T) { + ints := []int{ + 1, + 2, + } + int8s := []int8{ + 125, + -128, + } + int16s := []int16{ + 32000, + -32000, + } + int32s := []int32{ + 1000000, + -2000000, + } + int64s := []int64{ + 922337203685477, + -922337203685477, + } + + payload := &OnePayload{ + Data: &Node{ + Type: "number-arrays", + Attributes: map[string]interface{}{ + "ints": ints, + "8-ints": int8s, + "16-ints": int16s, + "32-ints": int32s, + "64-ints": int64s, + }, + }, + } + + in := bytes.NewBuffer(nil) + json.NewEncoder(in).Encode(payload) + + out := new(NumberArrays) + + if err := UnmarshalPayload(in, out); err != nil { + t.Fatal(err) + } + + if out.Ints[0] != ints[0] { + t.Fatal("Parsing the first int failed") + } + + if out.Ints[1] != ints[1] { + t.Fatal("Parsing the second int failed") + } + + if out.Int8s[0] != int8s[0] { + t.Fatal("Parsing the first int8 failed") + } + + if out.Int8s[1] != int8s[1] { + t.Fatal("Parsing the second int8 failed") + } + + if out.Int16s[0] != int16s[0] { + t.Fatal("Parsing the first int16 failed") + } + + if out.Int16s[1] != int16s[1] { + t.Fatal("Parsing the second int16 failed") + } + + if out.Int32s[0] != int32s[0] { + t.Fatal("Parsing the first int32 failed") + } + + if out.Int32s[1] != int32s[1] { + t.Fatal("Parsing the second int32 failed") + } + + if out.Int64s[0] != int64s[0] { + t.Fatal("Parsing the first int64 failed") + } + + if out.Int64s[1] != int64s[1] { + t.Fatal("Parsing the second int64 failed") + } +} + +func TestUnmarshalParsesIntPtrArray(t *testing.T) { + ints := []int{ + 1, + 2, + } + int8s := []int8{ + 125, + -128, + } + int16s := []int16{ + 32000, + -32000, + } + int32s := []int32{ + 1000000, + -2000000, + } + int64s := []int64{ + 922337203685477, + -922337203685477, + } + + payload := &OnePayload{ + Data: &Node{ + Type: "number-ptr-arrays", + Attributes: map[string]interface{}{ + "ints": ints, + "8-ints": int8s, + "16-ints": int16s, + "32-ints": int32s, + "64-ints": int64s, + }, + }, + } + + in := bytes.NewBuffer(nil) + json.NewEncoder(in).Encode(payload) + + out := new(NumberPtrArrays) + + if err := UnmarshalPayload(in, out); err != nil { + t.Fatal(err) + } + + if *out.Ints[0] != ints[0] { + t.Fatal("Parsing the first int failed") + } + + if *out.Ints[1] != ints[1] { + t.Fatal("Parsing the second int failed") + } + + if *out.Int8s[0] != int8s[0] { + t.Fatal("Parsing the first int8 failed") + } + + if *out.Int8s[1] != int8s[1] { + t.Fatal("Parsing the second int8 failed") + } + + if *out.Int16s[0] != int16s[0] { + t.Fatal("Parsing the first int16 failed") + } + + if *out.Int16s[1] != int16s[1] { + t.Fatal("Parsing the second int16 failed") + } + + if *out.Int32s[0] != int32s[0] { + t.Fatal("Parsing the first int32 failed") + } + + if *out.Int32s[1] != int32s[1] { + t.Fatal("Parsing the second int32 failed") + } + + if *out.Int64s[0] != int64s[0] { + t.Fatal("Parsing the first int64 failed") + } + + if *out.Int64s[1] != int64s[1] { + t.Fatal("Parsing the second int64 failed") + } +} + +func TestUnmarshalParsesUIntArray(t *testing.T) { + uints := []uint{ + 1, + 2, + } + uint8s := []uint8{ + 1, + 2, + } + uint16s := []uint16{ + 32000, + 64000, + } + uint32s := []uint32{ + 1000000, + 2000000, + } + uint64s := []uint64{ + 922337203685477, + 184467440737095, + } + + payload := &OnePayload{ + Data: &Node{ + Type: "number-arrays", + Attributes: map[string]interface{}{ + "uints": uints, + "uint8s": uint8s, + "uint16s": uint16s, + "uint32s": uint32s, + "uint64s": uint64s, + }, + }, + } + + in := bytes.NewBuffer(nil) + json.NewEncoder(in).Encode(payload) + + out := new(NumberArrays) + + if err := UnmarshalPayload(in, out); err != nil { + t.Fatal(err) + } + + if out.UInts[0] != 1 { + t.Fatal("Parsing the first uint failed") + } + + if out.UInts[1] != 2 { + t.Fatal("Parsing the second uint failed") + } + + if out.UInt8s[0] != uint8s[0] { + t.Fatal("Parsing the first uint8 failed") + } + + if out.UInt8s[1] != uint8s[1] { + t.Fatal("Parsing the second uint8 failed") + } + + if out.UInt16s[0] != uint16s[0] { + t.Fatal("Parsing the first uint16 failed") + } + + if out.UInt16s[1] != uint16s[1] { + t.Fatal("Parsing the second uint16 failed") + } + + if out.UInt32s[0] != uint32s[0] { + t.Fatal("Parsing the first uint32 failed") + } + + if out.UInt32s[1] != uint32s[1] { + t.Fatal("Parsing the second uint32 failed") + } + + if out.UInt64s[0] != uint64s[0] { + t.Fatal("Parsing the first uint64 failed") + } + + if out.UInt64s[1] != uint64s[1] { + t.Fatal("Parsing the second uint64 failed") + } +} + +func TestUnmarshalParsesUIntPtrArray(t *testing.T) { + uints := []uint{ + 1, + 2, + } + uint8s := []uint8{ + 1, + 2, + } + uint16s := []uint16{ + 32000, + 64000, + } + uint32s := []uint32{ + 1000000, + 2000000, + } + uint64s := []uint64{ + 922337203685477, + 184467440737095, + } + + payload := &OnePayload{ + Data: &Node{ + Type: "number-ptr-arrays", + Attributes: map[string]interface{}{ + "uints": uints, + "uint8s": uint8s, + "uint16s": uint16s, + "uint32s": uint32s, + "uint64s": uint64s, + }, + }, + } + + in := bytes.NewBuffer(nil) + json.NewEncoder(in).Encode(payload) + + out := new(NumberPtrArrays) + + if err := UnmarshalPayload(in, out); err != nil { + t.Fatal(err) + } + + if *out.UInts[0] != 1 { + t.Fatal("Parsing the first uint failed") + } + + if *out.UInts[1] != 2 { + t.Fatal("Parsing the second uint failed") + } + + if *out.UInt8s[0] != uint8s[0] { + t.Fatal("Parsing the first uint8 failed") + } + + if *out.UInt8s[1] != uint8s[1] { + t.Fatal("Parsing the second uint8 failed") + } + + if *out.UInt16s[0] != uint16s[0] { + t.Fatal("Parsing the first uint16 failed") + } + + if *out.UInt16s[1] != uint16s[1] { + t.Fatal("Parsing the second uint16 failed") + } + + if *out.UInt32s[0] != uint32s[0] { + t.Fatal("Parsing the first uint32 failed") + } + + if *out.UInt32s[1] != uint32s[1] { + t.Fatal("Parsing the second uint32 failed") + } + + if *out.UInt64s[0] != uint64s[0] { + t.Fatal("Parsing the first uint64 failed") + } + + if *out.UInt64s[1] != uint64s[1] { + t.Fatal("Parsing the second uint64 failed") + } +} + +func TestUnmarshalParsesFloatArray(t *testing.T) { + floats := []float32{ + 1.5, + 2.4, + } + + payload := &OnePayload{ + Data: &Node{ + Type: "number-arrays", + Attributes: map[string]interface{}{ + "floats": floats, + }, + }, + } + + in := bytes.NewBuffer(nil) + json.NewEncoder(in).Encode(payload) + + out := new(NumberArrays) + + if err := UnmarshalPayload(in, out); err != nil { + t.Fatal(err) + } + + if out.Floats[0] != 1.5 { + t.Fatal("Parsing the first float failed") + } + + if out.Floats[1] != 2.4 { + t.Fatal("Parsing the second float failed") + } +} + +func TestUnmarshalParsesFloatPtrArray(t *testing.T) { + floats := []float32{ + 1.5, + 2.4, + } + + payload := &OnePayload{ + Data: &Node{ + Type: "number-ptr-arrays", + Attributes: map[string]interface{}{ + "floats": floats, + }, + }, + } + + in := bytes.NewBuffer(nil) + json.NewEncoder(in).Encode(payload) + + out := new(NumberPtrArrays) + + if err := UnmarshalPayload(in, out); err != nil { + t.Fatal(err) + } + + if *out.Floats[0] != 1.5 { + t.Fatal("Parsing the first float failed") + } + + if *out.Floats[1] != 2.4 { + t.Fatal("Parsing the second float failed") + } +} + +func TestUnmarshalParsesDoubleArray(t *testing.T) { + doubles := []float64{ + 123456789.5, + 987654321.5, + } + + payload := &OnePayload{ + Data: &Node{ + Type: "number-arrays", + Attributes: map[string]interface{}{ + "doubles": doubles, + }, + }, + } + + in := bytes.NewBuffer(nil) + json.NewEncoder(in).Encode(payload) + + out := new(NumberArrays) + + if err := UnmarshalPayload(in, out); err != nil { + t.Fatal(err) + } + + if out.Doubles[0] != 123456789.5 { + t.Fatal("Parsing the first double failed") + } + + if out.Doubles[1] != 987654321.5 { + t.Fatal("Parsing the second double failed") + } +} + +func TestUnmarshalParsesDoublePtrArray(t *testing.T) { + doubles := []float64{ + 123456789.5, + 987654321.5, + } + + payload := &OnePayload{ + Data: &Node{ + Type: "number-ptr-arrays", + Attributes: map[string]interface{}{ + "doubles": doubles, + }, + }, + } + + in := bytes.NewBuffer(nil) + json.NewEncoder(in).Encode(payload) + + out := new(NumberPtrArrays) + + if err := UnmarshalPayload(in, out); err != nil { + t.Fatal(err) + } + + if *out.Doubles[0] != 123456789.5 { + t.Fatal("Parsing the first double failed") + } + + if *out.Doubles[1] != 987654321.5 { + t.Fatal("Parsing the second double failed") + } +} + +func TestUnmarshalParsesISO8601Array(t *testing.T) { + timestamps := []string{ + "2016-08-17T08:27:12Z", + "2016-08-18T08:27:12Z", + } + + payload := &OnePayload{ + Data: &Node{ + Type: "timestamp-arrays", + Attributes: map[string]interface{}{ + "timestamps": timestamps, + }, + }, + } + + in := bytes.NewBuffer(nil) + json.NewEncoder(in).Encode(payload) + + out := new(Timestamps) + + if err := UnmarshalPayload(in, out); err != nil { + t.Fatal(err) + } + + first := time.Date(2016, 8, 17, 8, 27, 12, 0, time.UTC) + second := time.Date(2016, 8, 18, 8, 27, 12, 0, time.UTC) + + if !out.Time[0].Equal(first) { + t.Fatal("Parsing the first ISO8601 timestamp failed") + } + + if !out.Time[1].Equal(second) { + t.Fatal("Parsing the second ISO8601 timestamp failed") + } +} + +func TestUnmarshalParsesISO8601TimePointerArray(t *testing.T) { + timestamps := []string{ + "2016-08-17T08:27:12Z", + "2016-08-18T08:27:12Z", + } + + payload := &OnePayload{ + Data: &Node{ + Type: "timestamps-arrays", + Attributes: map[string]interface{}{ + "next": timestamps, + }, + }, + } + + in := bytes.NewBuffer(nil) + json.NewEncoder(in).Encode(payload) + + out := new(Timestamps) + + if err := UnmarshalPayload(in, out); err != nil { + t.Fatal(err) + } + + expected := time.Date(2016, 8, 17, 8, 27, 12, 0, time.UTC) + + if !out.Next[0].Equal(expected) { + t.Fatal("Parsing the ISO8601 timestamp failed") + } +} + func TestUnmarshalParsesISO8601(t *testing.T) { payload := &OnePayload{ Data: &Node{