Skip to content

Commit ce83d9e

Browse files
authored
Merge pull request #1 from zillow/add-support-for-json-encoding-number
Add support for json.Number
2 parents f6f7691 + a2433c1 commit ce83d9e

File tree

3 files changed

+72
-6
lines changed

3 files changed

+72
-6
lines changed

decode_test.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,14 @@ var unmarshalTests = []struct {
784784
"---\nhello\n...\n}not yaml",
785785
"hello",
786786
},
787+
{
788+
"a: 5\n",
789+
&struct{ A jsonNumberT }{"5"},
790+
},
791+
{
792+
"a: 5.5\n",
793+
&struct{ A jsonNumberT }{"5.5"},
794+
},
787795

788796
// Comment scan exhausting the input buffer (issue #469).
789797
{
@@ -947,7 +955,7 @@ var unmarshalErrorTests = []struct {
947955
{"%TAG !%79! tag:yaml.org,2002:\n---\nv: !%79!int '1'", "yaml: did not find expected whitespace"},
948956
{"a:\n 1:\nb\n 2:", ".*could not find expected ':'"},
949957
{"a: 1\nb: 2\nc 2\nd: 3\n", "^yaml: line 3: could not find expected ':'$"},
950-
{"#\n-\n{", "yaml: line 3: could not find expected ':'"}, // Issue #665
958+
{"#\n-\n{", "yaml: line 3: could not find expected ':'"}, // Issue #665
951959
{"0: [:!00 \xef", "yaml: incomplete UTF-8 octet sequence"}, // Issue #666
952960
{
953961
"a: &a [00,00,00,00,00,00,00,00,00]\n" +
@@ -1482,7 +1490,7 @@ func (s *S) TestMergeNestedStruct(c *C) {
14821490
// 2) A simple implementation might attempt to handle the key skipping
14831491
// directly by iterating over the merging map without recursion, but
14841492
// there are more complex cases that require recursion.
1485-
//
1493+
//
14861494
// Quick summary of the fields:
14871495
//
14881496
// - A must come from outer and not overriden
@@ -1498,7 +1506,7 @@ func (s *S) TestMergeNestedStruct(c *C) {
14981506
A, B, C int
14991507
}
15001508
type Outer struct {
1501-
D, E int
1509+
D, E int
15021510
Inner Inner
15031511
Inline map[string]int `yaml:",inline"`
15041512
}
@@ -1516,10 +1524,10 @@ func (s *S) TestMergeNestedStruct(c *C) {
15161524
// Repeat test with a map.
15171525

15181526
var testm map[string]interface{}
1519-
var wantm = map[string]interface {} {
1520-
"f": 60,
1527+
var wantm = map[string]interface{}{
1528+
"f": 60,
15211529
"inner": map[string]interface{}{
1522-
"a": 10,
1530+
"a": 10,
15231531
},
15241532
"d": 40,
15251533
"e": 50,

encode.go

+28
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ import (
2828
"unicode/utf8"
2929
)
3030

31+
// jsonNumber is the interface of the encoding/json.Number datatype.
32+
// Repeating the interface here avoids a dependency on encoding/json, and also
33+
// supports other libraries like jsoniter, which use a similar datatype with
34+
// the same interface. Detecting this interface is useful when dealing with
35+
// structures containing json.Number, which is a string under the hood. The
36+
// encoder should prefer the use of Int64(), Float64() and string(), in that
37+
// order, when encoding this type.
38+
type jsonNumber interface {
39+
Float64() (float64, error)
40+
Int64() (int64, error)
41+
String() string
42+
}
43+
3144
type encoder struct {
3245
emitter yaml_emitter_t
3346
event yaml_event_t
@@ -127,6 +140,21 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
127140
}
128141
e.nodev(in.Addr())
129142
return
143+
case jsonNumber:
144+
integer, err := value.Int64()
145+
if err == nil {
146+
// In this case the json.Number is a valid int64
147+
in = reflect.ValueOf(integer)
148+
break
149+
}
150+
float, err := value.Float64()
151+
if err == nil {
152+
// In this case the json.Number is a valid float64
153+
in = reflect.ValueOf(float)
154+
break
155+
}
156+
// fallback case - no number could be obtained
157+
in = reflect.ValueOf(value.String())
130158
case time.Time:
131159
e.timev(tag, in)
132160
return

encode_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,24 @@ import (
3030
"gopkg.in/yaml.v3"
3131
)
3232

33+
type jsonNumberT string
34+
35+
func (j jsonNumberT) Int64() (int64, error) {
36+
val, err := strconv.Atoi(string(j))
37+
if err != nil {
38+
return 0, err
39+
}
40+
return int64(val), nil
41+
}
42+
43+
func (j jsonNumberT) Float64() (float64, error) {
44+
return strconv.ParseFloat(string(j), 64)
45+
}
46+
47+
func (j jsonNumberT) String() string {
48+
return string(j)
49+
}
50+
3351
var marshalIntTest = 123
3452

3553
var marshalTests = []struct {
@@ -407,6 +425,18 @@ var marshalTests = []struct {
407425
map[string]string{"a": "你好 #comment"},
408426
"a: '你好 #comment'\n",
409427
},
428+
{
429+
map[string]interface{}{"a": jsonNumberT("5")},
430+
"a: 5\n",
431+
},
432+
{
433+
map[string]interface{}{"a": jsonNumberT("100.5")},
434+
"a: 100.5\n",
435+
},
436+
{
437+
map[string]interface{}{"a": jsonNumberT("bogus")},
438+
"a: bogus\n",
439+
},
410440

411441
// Ensure MarshalYAML also gets called on the result of MarshalYAML itself.
412442
{

0 commit comments

Comments
 (0)