package jsoninfo import ( "encoding/json" "fmt" "reflect" ) // MarshalStrictStruct function: // * Marshals struct fields, ignoring MarshalJSON() and fields without 'json' tag. // * Correctly handles StrictStruct semantics. func MarshalStrictStruct(value StrictStruct) ([]byte, error) { encoder := NewObjectEncoder() if err := value.EncodeWith(encoder, value); err != nil { return nil, err } return encoder.Bytes() } type ObjectEncoder struct { result map[string]json.RawMessage } func NewObjectEncoder() *ObjectEncoder { return &ObjectEncoder{ result: make(map[string]json.RawMessage, 8), } } // Bytes returns the result of encoding. func (encoder *ObjectEncoder) Bytes() ([]byte, error) { return json.Marshal(encoder.result) } // EncodeExtension adds a key/value to the current JSON object. func (encoder *ObjectEncoder) EncodeExtension(key string, value interface{}) error { data, err := json.Marshal(value) if err != nil { return err } encoder.result[key] = data return nil } // EncodeExtensionMap adds all properties to the result. func (encoder *ObjectEncoder) EncodeExtensionMap(value map[string]json.RawMessage) error { if value != nil { result := encoder.result for k, v := range value { result[k] = v } } return nil } func (encoder *ObjectEncoder) EncodeStructFieldsAndExtensions(value interface{}) error { reflection := reflect.ValueOf(value) // Follow "encoding/json" semantics if reflection.Kind() != reflect.Ptr { // Panic because this is a clear programming error panic(fmt.Errorf("Value %s is not a pointer", reflection.Type().String())) } if reflection.IsNil() { // Panic because this is a clear programming error panic(fmt.Errorf("Value %s is nil", reflection.Type().String())) } // Take the element reflection = reflection.Elem() // Obtain typeInfo typeInfo := GetTypeInfo(reflection.Type()) // Declare result result := encoder.result // Supported fields iteration: for _, field := range typeInfo.Fields { // Fields without JSON tag are ignored if !field.HasJSONTag { continue } // Marshal fieldValue := reflection.FieldByIndex(field.Index) if v, ok := fieldValue.Interface().(json.Marshaler); ok { if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { if field.JSONOmitEmpty { continue iteration } result[field.JSONName] = []byte("null") continue } fieldData, err := v.MarshalJSON() if err != nil { return err } result[field.JSONName] = fieldData continue } switch fieldValue.Kind() { case reflect.Ptr, reflect.Interface: if fieldValue.IsNil() { if field.JSONOmitEmpty { continue iteration } result[field.JSONName] = []byte("null") continue } case reflect.Struct: case reflect.Map: if field.JSONOmitEmpty && (fieldValue.IsNil() || fieldValue.Len() == 0) { continue iteration } case reflect.Slice: if field.JSONOmitEmpty && fieldValue.Len() == 0 { continue iteration } case reflect.Bool: x := fieldValue.Bool() if field.JSONOmitEmpty && !x { continue iteration } s := "false" if x { s = "true" } result[field.JSONName] = []byte(s) continue iteration case reflect.Int64, reflect.Int, reflect.Int32: if field.JSONOmitEmpty && fieldValue.Int() == 0 { continue iteration } case reflect.Uint64, reflect.Uint, reflect.Uint32: if field.JSONOmitEmpty && fieldValue.Uint() == 0 { continue iteration } case reflect.Float64: if field.JSONOmitEmpty && fieldValue.Float() == 0.0 { continue iteration } case reflect.String: if field.JSONOmitEmpty && len(fieldValue.String()) == 0 { continue iteration } default: panic(fmt.Errorf("Field '%s' has unsupported type %s", field.JSONName, field.Type.String())) } // No special treament is needed // Use plain old "encoding/json".Marshal fieldData, err := json.Marshal(fieldValue.Addr().Interface()) if err != nil { return err } result[field.JSONName] = fieldData } return nil }