Skip to content
This repository was archived by the owner on Apr 1, 2025. It is now read-only.

Commit 51d6538

Browse files
echlebekniemeyer
authored andcommitted
Add support for json.Number. (#414)
This commit adds support for encoding json.Number as a number instead of as a string, which is the underlying type of json.Number. Care has been taken to not introduce a dependency on the encoding/json package, by using an interface that specifies the methods of json.Number instead of the datatype itself. This also means that other packages that use a similar datatype will be supported. (Like json-iterator) This is useful for tools that wish to convert JSON data into YAML data by deserializing JSON into a map[string]interface{}, and use the json.Encoder's UseNumber() method, or structs that use the json.Number datatype and also wish to support yaml.
1 parent 5420a8b commit 51d6538

File tree

3 files changed

+66
-0
lines changed

3 files changed

+66
-0
lines changed

decode_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,14 @@ var unmarshalTests = []struct {
714714
"---\nhello\n...\n}not yaml",
715715
"hello",
716716
},
717+
{
718+
"a: 5\n",
719+
&struct{ A jsonNumberT }{"5"},
720+
},
721+
{
722+
"a: 5.5\n",
723+
&struct{ A jsonNumberT }{"5.5"},
724+
},
717725
}
718726

719727
type M map[interface{}]interface{}

encode.go

+28
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ import (
1313
"unicode/utf8"
1414
)
1515

16+
// jsonNumber is the interface of the encoding/json.Number datatype.
17+
// Repeating the interface here avoids a dependency on encoding/json, and also
18+
// supports other libraries like jsoniter, which use a similar datatype with
19+
// the same interface. Detecting this interface is useful when dealing with
20+
// structures containing json.Number, which is a string under the hood. The
21+
// encoder should prefer the use of Int64(), Float64() and string(), in that
22+
// order, when encoding this type.
23+
type jsonNumber interface {
24+
Float64() (float64, error)
25+
Int64() (int64, error)
26+
String() string
27+
}
28+
1629
type encoder struct {
1730
emitter yaml_emitter_t
1831
event yaml_event_t
@@ -89,6 +102,21 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
89102
}
90103
iface := in.Interface()
91104
switch m := iface.(type) {
105+
case jsonNumber:
106+
integer, err := m.Int64()
107+
if err == nil {
108+
// In this case the json.Number is a valid int64
109+
in = reflect.ValueOf(integer)
110+
break
111+
}
112+
float, err := m.Float64()
113+
if err == nil {
114+
// In this case the json.Number is a valid float64
115+
in = reflect.ValueOf(float)
116+
break
117+
}
118+
// fallback case - no number could be obtained
119+
in = reflect.ValueOf(m.String())
92120
case time.Time, *time.Time:
93121
// Although time.Time implements TextMarshaler,
94122
// we don't want to treat it as a string for YAML

encode_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@ import (
1515
"gopkg.in/yaml.v2"
1616
)
1717

18+
type jsonNumberT string
19+
20+
func (j jsonNumberT) Int64() (int64, error) {
21+
val, err := strconv.Atoi(string(j))
22+
if err != nil {
23+
return 0, err
24+
}
25+
return int64(val), nil
26+
}
27+
28+
func (j jsonNumberT) Float64() (float64, error) {
29+
return strconv.ParseFloat(string(j), 64)
30+
}
31+
32+
func (j jsonNumberT) String() string {
33+
return string(j)
34+
}
35+
1836
var marshalIntTest = 123
1937

2038
var marshalTests = []struct {
@@ -367,6 +385,18 @@ var marshalTests = []struct {
367385
map[string]string{"a": "你好 #comment"},
368386
"a: '你好 #comment'\n",
369387
},
388+
{
389+
map[string]interface{}{"a": jsonNumberT("5")},
390+
"a: 5\n",
391+
},
392+
{
393+
map[string]interface{}{"a": jsonNumberT("100.5")},
394+
"a: 100.5\n",
395+
},
396+
{
397+
map[string]interface{}{"a": jsonNumberT("bogus")},
398+
"a: bogus\n",
399+
},
370400
}
371401

372402
func (s *S) TestMarshal(c *C) {

0 commit comments

Comments
 (0)