1package jsoninfo
2
3import (
4	"encoding/json"
5	"fmt"
6	"reflect"
7)
8
9// MarshalStrictStruct function:
10//   * Marshals struct fields, ignoring MarshalJSON() and fields without 'json' tag.
11//   * Correctly handles StrictStruct semantics.
12func MarshalStrictStruct(value StrictStruct) ([]byte, error) {
13	encoder := NewObjectEncoder()
14	if err := value.EncodeWith(encoder, value); err != nil {
15		return nil, err
16	}
17	return encoder.Bytes()
18}
19
20type ObjectEncoder struct {
21	result map[string]json.RawMessage
22}
23
24func NewObjectEncoder() *ObjectEncoder {
25	return &ObjectEncoder{
26		result: make(map[string]json.RawMessage, 8),
27	}
28}
29
30// Bytes returns the result of encoding.
31func (encoder *ObjectEncoder) Bytes() ([]byte, error) {
32	return json.Marshal(encoder.result)
33}
34
35// EncodeExtension adds a key/value to the current JSON object.
36func (encoder *ObjectEncoder) EncodeExtension(key string, value interface{}) error {
37	data, err := json.Marshal(value)
38	if err != nil {
39		return err
40	}
41	encoder.result[key] = data
42	return nil
43}
44
45// EncodeExtensionMap adds all properties to the result.
46func (encoder *ObjectEncoder) EncodeExtensionMap(value map[string]json.RawMessage) error {
47	if value != nil {
48		result := encoder.result
49		for k, v := range value {
50			result[k] = v
51		}
52	}
53	return nil
54}
55
56func (encoder *ObjectEncoder) EncodeStructFieldsAndExtensions(value interface{}) error {
57	reflection := reflect.ValueOf(value)
58
59	// Follow "encoding/json" semantics
60	if reflection.Kind() != reflect.Ptr {
61		// Panic because this is a clear programming error
62		panic(fmt.Errorf("value %s is not a pointer", reflection.Type().String()))
63	}
64	if reflection.IsNil() {
65		// Panic because this is a clear programming error
66		panic(fmt.Errorf("value %s is nil", reflection.Type().String()))
67	}
68
69	// Take the element
70	reflection = reflection.Elem()
71
72	// Obtain typeInfo
73	typeInfo := GetTypeInfo(reflection.Type())
74
75	// Declare result
76	result := encoder.result
77
78	// Supported fields
79iteration:
80	for _, field := range typeInfo.Fields {
81		// Fields without JSON tag are ignored
82		if !field.HasJSONTag {
83			continue
84		}
85
86		// Marshal
87		fieldValue := reflection.FieldByIndex(field.Index)
88		if v, ok := fieldValue.Interface().(json.Marshaler); ok {
89			if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
90				if field.JSONOmitEmpty {
91					continue iteration
92				}
93				result[field.JSONName] = []byte("null")
94				continue
95			}
96			fieldData, err := v.MarshalJSON()
97			if err != nil {
98				return err
99			}
100			result[field.JSONName] = fieldData
101			continue
102		}
103		switch fieldValue.Kind() {
104		case reflect.Ptr, reflect.Interface:
105			if fieldValue.IsNil() {
106				if field.JSONOmitEmpty {
107					continue iteration
108				}
109				result[field.JSONName] = []byte("null")
110				continue
111			}
112		case reflect.Struct:
113		case reflect.Map:
114			if field.JSONOmitEmpty && (fieldValue.IsNil() || fieldValue.Len() == 0) {
115				continue iteration
116			}
117		case reflect.Slice:
118			if field.JSONOmitEmpty && fieldValue.Len() == 0 {
119				continue iteration
120			}
121		case reflect.Bool:
122			x := fieldValue.Bool()
123			if field.JSONOmitEmpty && !x {
124				continue iteration
125			}
126			s := "false"
127			if x {
128				s = "true"
129			}
130			result[field.JSONName] = []byte(s)
131			continue iteration
132		case reflect.Int64, reflect.Int, reflect.Int32:
133			if field.JSONOmitEmpty && fieldValue.Int() == 0 {
134				continue iteration
135			}
136		case reflect.Uint64, reflect.Uint, reflect.Uint32:
137			if field.JSONOmitEmpty && fieldValue.Uint() == 0 {
138				continue iteration
139			}
140		case reflect.Float64:
141			if field.JSONOmitEmpty && fieldValue.Float() == 0.0 {
142				continue iteration
143			}
144		case reflect.String:
145			if field.JSONOmitEmpty && len(fieldValue.String()) == 0 {
146				continue iteration
147			}
148		default:
149			panic(fmt.Errorf("field %q has unsupported type %s", field.JSONName, field.Type.String()))
150		}
151
152		// No special treament is needed
153		// Use plain old "encoding/json".Marshal
154		fieldData, err := json.Marshal(fieldValue.Addr().Interface())
155		if err != nil {
156			return err
157		}
158		result[field.JSONName] = fieldData
159	}
160
161	return nil
162}
163