Skip to content

Commit b8af015

Browse files
committed
merge prerelease branch
1 parent d5402d1 commit b8af015

File tree

6 files changed

+76
-10
lines changed

6 files changed

+76
-10
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
All notable changes to the project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
44

5+
## [2.5.1] - 2022-06-30
6+
### Changed:
7+
- If you create an `ldvalue.Value` with the `ldvalue.Raw(json.RawMessage)` constructor, and you pass a zero-length or nil value to the constructor, and then encode the `Value` to JSON with `json.Marshal` or an equivalent method, the JSON output will now be `null` (that is, the literal characters `null` representing a JSON null value). Previously it would have been a zero-length string, which is not valid as the JSON encoding of any value and could cause the SDK to output a malformed JSON document if the document contained such a value.
8+
59
## [2.5.0] - 2021-10-14
610
_This release was unintended and can be ignored. It contains no code changes, only changes to the CI build._
711

ldvalue/value_base.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ func String(value string) Value {
7979
}
8080

8181
// Raw creates an unparsed JSON Value.
82+
//
83+
// This constructor will store the json.RawMessage value as-is without syntax validation, and will set
84+
// the type of the Value to ldvalue.RawType. That means, for instance, that ldvalue.Raw(json.RawMessage("true"))
85+
// is not logically equivalent to ldvalue.Bool(true), even though they will produce the same result if
86+
// they are re-encoded to JSON.
87+
//
88+
// That also means that if you pass malformed data that is not valid JSON, you will get malformed data if
89+
// it is re-encoded to JSON. It is the caller's responsibility to make sure the json.RawMessage really is
90+
// valid JSON. However, since it is easy to mistakenly write json.RawMessage(nil) (a zero-length value)
91+
// when what is really meant is a JSON null value, the ldvalue.Value JSON encoder will detect any use of
92+
// json.RawMessage(nil) or json.RawMessage("") and transparently convert it to "null" when it is
93+
// being encoded to JSON.
8294
func Raw(value json.RawMessage) Value {
8395
return Value{valueType: RawType, stringValue: string(value)}
8496
}
@@ -355,6 +367,10 @@ func (v Value) AsOptionalString() OptionalString {
355367
//
356368
// If the value was originally created from a RawMessage, it returns the same value. For all other
357369
// values, it converts the value to its JSON representation and returns that representation.
370+
//
371+
// Note that the ldvalue.Raw(json.RawMessage) constructor does not do any syntax validation, so
372+
// if you create a Value from a malformed string such as ldvalue.Raw(json.RawMessage("{{{")), you
373+
// will get back the same string from AsRaw().
358374
func (v Value) AsRaw() json.RawMessage {
359375
if v.valueType == RawType {
360376
return json.RawMessage(v.stringValue)

ldvalue/value_serialization.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ func (v Value) MarshalJSON() ([]byte, error) {
7575
case ObjectType:
7676
return v.objectValue.MarshalJSON()
7777
case RawType:
78+
if len(v.stringValue) == 0 {
79+
return nullAsJSONBytes, nil
80+
// we don't check for other kinds of malformed JSON here, but if it was nil/"" we can assume they meant null
81+
}
7882
return []byte(v.stringValue), nil
7983
}
8084
return nil, errors.New("unknown data type") // should not be possible
@@ -135,6 +139,10 @@ func (v Value) WriteToJSONWriter(w *jwriter.Writer) {
135139
case ObjectType:
136140
v.objectValue.WriteToJSONWriter(w)
137141
case RawType:
138-
w.Raw([]byte(v.stringValue))
142+
if len(v.stringValue) == 0 {
143+
w.Null() // we don't check for other kinds of malformed JSON here, but if it was nil/"" we can assume they meant null
144+
} else {
145+
w.Raw([]byte(v.stringValue))
146+
}
139147
}
140148
}

ldvalue/value_serialization_easyjson.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ func (v Value) MarshalEasyJSON(writer *ej_jwriter.Writer) {
3535
v.arrayValue.MarshalEasyJSON(writer)
3636
case ObjectType:
3737
v.objectValue.MarshalEasyJSON(writer)
38+
case RawType:
39+
if len(v.stringValue) == 0 {
40+
writer.Raw(nullAsJSONBytes, nil) // see Value.MarshalJSON
41+
} else {
42+
writer.RawString(v.stringValue)
43+
}
3844
}
3945
}
4046

ldvalue/value_serialization_easyjson_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,24 @@ func TestEasyJsonUnmarshalErrorConditions(t *testing.T) {
6464
})
6565
}
6666
}
67+
68+
func TestEasyJsonMarshalRaw(t *testing.T) {
69+
// This is separate from the MarshalUnmarshal test because you never get a Raw when you unmarshal.
70+
for _, params := range []struct {
71+
desc string
72+
input json.RawMessage
73+
output string
74+
}{
75+
{"valid JSON", json.RawMessage(`{"a":1}`), `{"a":1}`},
76+
{"zero-length", json.RawMessage{}, `null`},
77+
{"nil", json.RawMessage(nil), `null`},
78+
} {
79+
t.Run(params.desc, func(t *testing.T) {
80+
value := Raw(params.input)
81+
82+
j, err := easyjson.Marshal(value)
83+
assert.NoError(t, err)
84+
assert.Equal(t, params.output, string(j))
85+
})
86+
}
87+
}

ldvalue/value_serialization_test.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,28 @@ func TestJsonMarshalUnmarshal(t *testing.T) {
6262

6363
func TestMarshalRaw(t *testing.T) {
6464
// This is separate from the MarshalUnmarshal test because you never get a Raw when you unmarshal.
65-
s := `{"a":1}`
66-
value := Raw(json.RawMessage(s))
65+
for _, params := range []struct {
66+
desc string
67+
input json.RawMessage
68+
output string
69+
}{
70+
{"valid JSON", json.RawMessage(`{"a":1}`), `{"a":1}`},
71+
{"zero-length", json.RawMessage{}, `null`},
72+
{"nil", json.RawMessage(nil), `null`},
73+
} {
74+
t.Run(params.desc, func(t *testing.T) {
75+
value := Raw(params.input)
6776

68-
bytes, err := json.Marshal(value)
69-
assert.NoError(t, err)
70-
assert.Equal(t, s, string(bytes))
77+
bytes, err := json.Marshal(value)
78+
assert.NoError(t, err)
79+
assert.Equal(t, params.output, string(bytes))
7180

72-
w := jwriter.NewWriter()
73-
value.WriteToJSONWriter(&w)
74-
assert.NoError(t, w.Error())
75-
assert.Equal(t, s, string(w.Bytes()))
81+
w := jwriter.NewWriter()
82+
value.WriteToJSONWriter(&w)
83+
assert.NoError(t, w.Error())
84+
assert.Equal(t, params.output, string(w.Bytes()))
85+
})
86+
}
7687
}
7788

7889
func TestUnmarshalErrorConditions(t *testing.T) {

0 commit comments

Comments
 (0)