-
Notifications
You must be signed in to change notification settings - Fork 3
/
value_serialization.go
179 lines (171 loc) · 4.95 KB
/
value_serialization.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package ldvalue
import (
"encoding/json"
"errors"
"strconv"
"gopkg.in/launchdarkly/go-sdk-common.v2/jsonstream"
)
// This file contains methods for converting Value to and from JSON.
// Parse returns a Value parsed from a JSON string, or Null if it cannot be parsed.
//
// This is simply a shortcut for calling json.Unmarshal and disregarding errors. It is meant for
// use in test scenarios where malformed data is not a concern.
func Parse(jsonData []byte) Value {
var v Value
if err := json.Unmarshal(jsonData, &v); err != nil {
return Null()
}
return v
}
// JSONString returns the JSON representation of the value.
func (v Value) JSONString() string {
// The following is somewhat redundant with json.Marshal, but it avoids the overhead of
// converting between byte arrays and strings.
switch v.valueType {
case NullType:
return nullAsJSON
case BoolType:
if v.boolValue {
return trueString
}
return falseString
case NumberType:
if v.IsInt() {
return strconv.Itoa(int(v.numberValue))
}
return strconv.FormatFloat(v.numberValue, 'f', -1, 64)
}
// For all other types, we rely on our custom marshaller.
bytes, _ := json.Marshal(v)
// It shouldn't be possible for marshalling to fail, because Value can only contain
// JSON-compatible types. But if it somehow did fail, bytes will be nil and we'll return
// an empty tring.
return string(bytes)
}
// MarshalJSON converts the Value to its JSON representation.
//
// Note that the "omitempty" tag for a struct field will not cause an empty Value field to be
// omitted; it will be output as null. If you want to completely omit a JSON property when there
// is no value, it must be a pointer; use AsPointer().
func (v Value) MarshalJSON() ([]byte, error) {
switch v.valueType {
case NullType:
return []byte(nullAsJSON), nil
case BoolType:
if v.boolValue {
return []byte(trueString), nil
}
return []byte(falseString), nil
case NumberType:
if v.IsInt() {
return []byte(strconv.Itoa(int(v.numberValue))), nil
}
return []byte(strconv.FormatFloat(v.numberValue, 'f', -1, 64)), nil
case StringType:
return json.Marshal(v.stringValue)
case ArrayType:
if v.immutableArrayValue == nil {
return json.Marshal([]Value{})
}
return json.Marshal(v.immutableArrayValue)
case ObjectType:
if v.immutableObjectValue == nil {
return json.Marshal(map[string]Value{})
}
return json.Marshal(v.immutableObjectValue)
case RawType:
return []byte(v.stringValue), nil
}
return nil, errors.New("unknown data type") // should not be possible
}
// UnmarshalJSON parses a Value from JSON.
func (v *Value) UnmarshalJSON(data []byte) error { //nolint:funlen // yes, we know it's a long function
if len(data) == 0 { // COVERAGE: should not be possible, parser doesn't pass empty slices to UnmarshalJSON
return errors.New("cannot parse empty data")
}
firstCh := data[0]
switch firstCh {
case 'n':
// Note that since Go 1.5, comparing a string to string([]byte) is optimized so it
// does not actually create a new string from the byte slice.
if string(data) == "null" {
*v = Null()
return nil
}
case 't', 'f':
if string(data) == trueString {
*v = Bool(true)
return nil
}
if string(data) == falseString {
*v = Bool(false)
return nil
}
case '"':
var s string
e := json.Unmarshal(data, &s)
if e == nil {
*v = String(s)
}
return e
case '[':
var a []Value
e := json.Unmarshal(data, &a)
if e == nil {
if len(a) == 0 {
a = nil // don't need to retain an empty array
}
*v = Value{valueType: ArrayType, immutableArrayValue: a}
}
return e
case '{':
var o map[string]Value
e := json.Unmarshal(data, &o)
if e == nil {
if len(o) == 0 {
o = nil // don't need to retain an empty map
}
*v = Value{valueType: ObjectType, immutableObjectValue: o}
}
return e
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': // note, JSON does not allow a leading '.'
var n float64
e := json.Unmarshal(data, &n)
if e == nil {
*v = Value{valueType: NumberType, numberValue: n}
}
return e
}
return &json.SyntaxError{} // COVERAGE: never happens, parser rejects the token earlier
}
// WriteToJSONBuffer provides JSON serialization for Value with the jsonstream API.
//
// The JSON output format is identical to what is produced by json.Marshal, but this implementation is
// more efficient when building output with JSONBuffer. See the jsonstream package for more details.
func (v Value) WriteToJSONBuffer(j *jsonstream.JSONBuffer) {
switch v.valueType {
case NullType:
j.WriteNull()
case BoolType:
j.WriteBool(v.boolValue)
case NumberType:
j.WriteFloat64(v.numberValue)
case StringType:
j.WriteString(v.stringValue)
case ArrayType:
j.BeginArray()
for _, vv := range v.immutableArrayValue {
vv.WriteToJSONBuffer(j)
}
j.EndArray()
case ObjectType:
j.BeginObject()
for k, vv := range v.immutableObjectValue {
j.WriteName(k)
vv.WriteToJSONBuffer(j)
}
j.EndObject()
case RawType:
j.WriteRaw([]byte(v.stringValue))
}
}