package jsonapi import ( "bytes" "encoding/json" "errors" "fmt" "io" "reflect" "strconv" "strings" "time" ) const ( unsuportedStructTagMsg = "Unsupported jsonapi tag annotation, %s" ) var ( // ErrInvalidTime is returned when a struct has a time.Time type field, but // the JSON value was not a unix timestamp integer. ErrInvalidTime = errors.New("Only numbers can be parsed as dates, unix timestamps") // ErrInvalidISO8601 is returned when a struct has a time.Time type field and includes // "iso8601" in the tag spec, but the JSON value was not an ISO8601 timestamp string. ErrInvalidISO8601 = errors.New("Only strings can be parsed as dates, ISO8601 timestamps") // ErrUnknownFieldNumberType is returned when the JSON value was a float // (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") // 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. ) // UnmarshalPayload converts an io into a struct instance using jsonapi tags on // struct fields. This method supports single request payloads only, at the // moment. Bulk creates and updates are not supported yet. // // Will Unmarshal embedded and sideloaded payloads. The latter is only possible if the // object graph is complete. That is, in the "relationships" data there are type and id, // keys that correspond to records in the "included" array. // // For example you could pass it, in, req.Body and, model, a BlogPost // struct instance to populate in an http handler, // // func CreateBlog(w http.ResponseWriter, r *http.Request) { // blog := new(Blog) // // if err := jsonapi.UnmarshalPayload(r.Body, blog); err != nil { // http.Error(w, err.Error(), 500) // return // } // // // ...do stuff with your blog... // // w.Header().Set("Content-Type", jsonapi.MediaType) // w.WriteHeader(201) // // if err := jsonapi.MarshalPayload(w, blog); err != nil { // http.Error(w, err.Error(), 500) // } // } // // // Visit https://github.com/google/jsonapi#create for more info. // // model interface{} should be a pointer to a struct. func UnmarshalPayload(in io.Reader, model interface{}) error { payload := new(OnePayload) if err := json.NewDecoder(in).Decode(payload); err != nil { return err } if payload.Included != nil { includedMap := make(map[string]*Node) for _, included := range payload.Included { key := fmt.Sprintf("%s,%s", included.Type, included.ID) includedMap[key] = included } return unmarshalNode(payload.Data, reflect.ValueOf(model), &includedMap) } return unmarshalNode(payload.Data, reflect.ValueOf(model), nil) } // UnmarshalManyPayload converts an io into a set of struct instances using // jsonapi tags on the type's struct fields. func UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error) { payload := new(ManyPayload) if err := json.NewDecoder(in).Decode(payload); err != nil { return nil, err } models := []interface{}{} // will be populated from the "data" includedMap := map[string]*Node{} // will be populate from the "included" if payload.Included != nil { for _, included := range payload.Included { key := fmt.Sprintf("%s,%s", included.Type, included.ID) includedMap[key] = included } } for _, data := range payload.Data { model := reflect.New(t.Elem()) err := unmarshalNode(data, model, &includedMap) if err != nil { return nil, err } models = append(models, model.Interface()) } return models, nil } // unmarshalNode handles embedded struct models from top to down. // it loops through the struct fields, handles attributes/relations at that level first // the handling the embedded structs are done last, so that you get the expected composition behavior // data (*Node) attributes are cleared on each success. // relations/sideloaded models use deeply copied Nodes (since those sideloaded models can be referenced in multiple relations) func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("data is not a jsonapi representation of '%v'", model.Type()) } }() modelValue := model.Elem() modelType := model.Type().Elem() type embedded struct { structField, model reflect.Value } embeddeds := []*embedded{} for i := 0; i < modelValue.NumField(); i++ { fieldType := modelType.Field(i) fieldValue := modelValue.Field(i) tag := fieldType.Tag.Get(annotationJSONAPI) // handle explicit ignore annotation if shouldIgnoreField(tag) { continue } // handles embedded structs if isEmbeddedStruct(fieldType) { embeddeds = append(embeddeds, &embedded{ model: reflect.ValueOf(fieldValue.Addr().Interface()), structField: fieldValue, }, ) continue } // handles pointers to embedded structs if isEmbeddedStructPtr(fieldType) { embeddeds = append(embeddeds, &embedded{ model: reflect.ValueOf(fieldValue.Interface()), structField: fieldValue, }, ) continue } // handle tagless; after handling embedded structs (which could be tagless) if tag == "" { continue } args := strings.Split(tag, annotationSeperator) // require atleast 1 if len(args) < 1 { return ErrBadJSONAPIStructTag } // args[0] == annotation switch args[0] { case annotationClientID: if err := handleClientIDUnmarshal(data, args, fieldValue); err != nil { return err } case annotationPrimary: if err := handlePrimaryUnmarshal(data, args, fieldType, fieldValue); err != nil { return err } case annotationAttribute: if err := handleAttributeUnmarshal(data, args, fieldType, fieldValue); err != nil { return err } case annotationRelation: if err := handleRelationUnmarshal(data, args, fieldValue, included); err != nil { return err } default: return fmt.Errorf(unsuportedStructTagMsg, args[0]) } } // handle embedded last for _, em := range embeddeds { // if nil, need to construct and rollback accordingly if em.model.IsNil() { copy := deepCopyNode(data) tmp := reflect.New(em.model.Type().Elem()) if err := unmarshalNode(copy, tmp, included); err != nil { return err } // had changes; assign value to struct field, replace orig node (data) w/ mutated copy if !reflect.DeepEqual(copy, data) { assign(em.structField, tmp) data = copy } } else { // handle non-nil scenarios if err := unmarshalNode(data, em.model, included); err != nil { return err } } } return nil } func handleClientIDUnmarshal(data *Node, args []string, fieldValue reflect.Value) error { if len(args) != 1 { return ErrBadJSONAPIStructTag } if data.ClientID == "" { return nil } // set value and clear clientID to denote it's already been processed fieldValue.Set(reflect.ValueOf(data.ClientID)) data.ClientID = "" return nil } func handlePrimaryUnmarshal(data *Node, args []string, fieldType reflect.StructField, fieldValue reflect.Value) error { if len(args) < 2 { return ErrBadJSONAPIStructTag } if data.ID == "" { return nil } // Check the JSON API Type if data.Type != args[1] { return fmt.Errorf( "Trying to Unmarshal an object of type %#v, but %#v does not match", data.Type, args[1], ) } // Deal with PTRS var kind reflect.Kind if fieldValue.Kind() == reflect.Ptr { kind = fieldType.Type.Elem().Kind() } else { kind = fieldType.Type.Kind() } var idValue reflect.Value // Handle String case if kind == reflect.String { // ID will have to be transmitted as a string per the JSON API spec idValue = reflect.ValueOf(data.ID) } else { // Value was not a string... only other supported type was a numeric, // which would have been sent as a float value. floatValue, err := strconv.ParseFloat(data.ID, 64) if err != nil { // Could not convert the value in the "id" attr to a float return ErrBadJSONAPIID } // Convert the numeric float to one of the supported ID numeric types // (int[8,16,32,64] or uint[8,16,32,64]) 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: // We had a JSON float (numeric), but our field was not one of the // allowed numeric types return ErrBadJSONAPIID } } // set value and clear ID to denote it's already been processed assign(fieldValue, idValue) data.ID = "" return nil } func handleRelationUnmarshal(data *Node, args []string, fieldValue reflect.Value, included *map[string]*Node) error { if len(args) < 2 { return ErrBadJSONAPIStructTag } if data.Relationships == nil || data.Relationships[args[1]] == nil { return nil } // to-one relationships handler := handleToOneRelationUnmarshal isSlice := fieldValue.Type().Kind() == reflect.Slice if isSlice { // to-many relationship handler = handleToManyRelationUnmarshal } v, err := handler(data.Relationships[args[1]], fieldValue.Type(), included) if err != nil { return err } // set only if there is a val since val can be null (e.g. to disassociate the relationship) if v != nil { fieldValue.Set(*v) } delete(data.Relationships, args[1]) return nil } // to-one relationships func handleToOneRelationUnmarshal(relationData interface{}, fieldType reflect.Type, included *map[string]*Node) (*reflect.Value, error) { relationship := new(RelationshipOneNode) buf := bytes.NewBuffer(nil) json.NewEncoder(buf).Encode(relationData) json.NewDecoder(buf).Decode(relationship) m := reflect.New(fieldType.Elem()) /* http://jsonapi.org/format/#document-resource-object-relationships http://jsonapi.org/format/#document-resource-object-linkage relationship can have a data node set to null (e.g. to disassociate the relationship) so unmarshal and set fieldValue only if data obj is not null */ if relationship.Data == nil { return nil, nil } if err := unmarshalNode( fullNode(relationship.Data, included), m, included, ); err != nil { return nil, err } return &m, nil } // to-many relationship func handleToManyRelationUnmarshal(relationData interface{}, fieldType reflect.Type, included *map[string]*Node) (*reflect.Value, error) { relationship := new(RelationshipManyNode) buf := bytes.NewBuffer(nil) json.NewEncoder(buf).Encode(relationData) json.NewDecoder(buf).Decode(relationship) models := reflect.New(fieldType).Elem() rData := relationship.Data for _, n := range rData { m := reflect.New(fieldType.Elem().Elem()) if err := unmarshalNode( fullNode(n, included), m, included, ); err != nil { return nil, err } models = reflect.Append(models, m) } return &models, nil } // TODO: break this out into smaller funcs func handleAttributeUnmarshal(data *Node, args []string, fieldType reflect.StructField, fieldValue reflect.Value) error { if len(args) < 2 { return ErrBadJSONAPIStructTag } attributes := data.Attributes if attributes == nil || len(data.Attributes) == 0 { return nil } var iso8601 bool if len(args) > 2 { for _, arg := range args[2:] { if arg == annotationISO8601 { iso8601 = true } } } val := attributes[args[1]] // continue if the attribute was not included in the request if val == nil { return nil } 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 { return ErrInvalidISO8601 } t, err := time.Parse(iso8601TimeFormat, tm) if err != nil { return ErrInvalidISO8601 } fieldValue.Set(reflect.ValueOf(t)) delete(data.Attributes, args[1]) 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)) delete(data.Attributes, args[1]) return nil } 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)) delete(data.Attributes, args[1]) return nil } if fieldValue.Type() == reflect.TypeOf(new(time.Time)) { 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)) delete(data.Attributes, args[1]) 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 } v := time.Unix(at, 0) t := &v fieldValue.Set(reflect.ValueOf(t)) delete(data.Attributes, args[1]) return nil } // 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) delete(data.Attributes, args[1]) return nil } // 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) delete(data.Attributes, args[1]) return nil } // As a final catch-all, ensure types line up to avoid a runtime panic. // Ignore interfaces since interfaces are poly if fieldValue.Kind() != reflect.Interface && fieldValue.Kind() != v.Kind() { return ErrInvalidType } // set val and clear attribute key so its not processed again fieldValue.Set(reflect.ValueOf(val)) delete(data.Attributes, args[1]) return nil } func fullNode(n *Node, included *map[string]*Node) *Node { includedKey := fmt.Sprintf("%s,%s", n.Type, n.ID) if included != nil && (*included)[includedKey] != nil { return deepCopyNode((*included)[includedKey]) } return deepCopyNode(n) } // 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 { field.Set(reflect.Indirect(value)) } }