162 lines
3.9 KiB
Go
162 lines
3.9 KiB
Go
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
|
|
}
|