Support encoding json.Number type (#923)
Co-authored-by: Thomas Pelletier <thomas@pelletier.codes>
This commit is contained in:
@@ -98,9 +98,9 @@ Given the following struct, let's see how to read it and write it as TOML:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
type MyConfig struct {
|
type MyConfig struct {
|
||||||
Version int
|
Version int
|
||||||
Name string
|
Name string
|
||||||
Tags []string
|
Tags []string
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ tags = ["go", "toml"]
|
|||||||
var cfg MyConfig
|
var cfg MyConfig
|
||||||
err := toml.Unmarshal([]byte(doc), &cfg)
|
err := toml.Unmarshal([]byte(doc), &cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
fmt.Println("version:", cfg.Version)
|
fmt.Println("version:", cfg.Version)
|
||||||
fmt.Println("name:", cfg.Name)
|
fmt.Println("name:", cfg.Name)
|
||||||
@@ -140,14 +140,14 @@ as a TOML document:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
cfg := MyConfig{
|
cfg := MyConfig{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
Name: "go-toml",
|
Name: "go-toml",
|
||||||
Tags: []string{"go", "toml"},
|
Tags: []string{"go", "toml"},
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := toml.Marshal(cfg)
|
b, err := toml.Marshal(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
fmt.Println(string(b))
|
fmt.Println(string(b))
|
||||||
|
|
||||||
@@ -175,17 +175,17 @@ the AST level. See https://pkg.go.dev/github.com/pelletier/go-toml/v2/unstable.
|
|||||||
Execution time speedup compared to other Go TOML libraries:
|
Execution time speedup compared to other Go TOML libraries:
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
|
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>Marshal/HugoFrontMatter-2</td><td>1.9x</td><td>2.2x</td></tr>
|
<tr><td>Marshal/HugoFrontMatter-2</td><td>1.9x</td><td>2.2x</td></tr>
|
||||||
<tr><td>Marshal/ReferenceFile/map-2</td><td>1.7x</td><td>2.1x</td></tr>
|
<tr><td>Marshal/ReferenceFile/map-2</td><td>1.7x</td><td>2.1x</td></tr>
|
||||||
<tr><td>Marshal/ReferenceFile/struct-2</td><td>2.2x</td><td>3.0x</td></tr>
|
<tr><td>Marshal/ReferenceFile/struct-2</td><td>2.2x</td><td>3.0x</td></tr>
|
||||||
<tr><td>Unmarshal/HugoFrontMatter-2</td><td>2.9x</td><td>2.7x</td></tr>
|
<tr><td>Unmarshal/HugoFrontMatter-2</td><td>2.9x</td><td>2.7x</td></tr>
|
||||||
<tr><td>Unmarshal/ReferenceFile/map-2</td><td>2.6x</td><td>2.7x</td></tr>
|
<tr><td>Unmarshal/ReferenceFile/map-2</td><td>2.6x</td><td>2.7x</td></tr>
|
||||||
<tr><td>Unmarshal/ReferenceFile/struct-2</td><td>4.6x</td><td>5.1x</td></tr>
|
<tr><td>Unmarshal/ReferenceFile/struct-2</td><td>4.6x</td><td>5.1x</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<details><summary>See more</summary>
|
<details><summary>See more</summary>
|
||||||
<p>The table above has the results of the most common use-cases. The table below
|
<p>The table above has the results of the most common use-cases. The table below
|
||||||
@@ -193,22 +193,22 @@ contains the results of all benchmarks, including unrealistic ones. It is
|
|||||||
provided for completeness.</p>
|
provided for completeness.</p>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
|
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>Marshal/SimpleDocument/map-2</td><td>1.8x</td><td>2.7x</td></tr>
|
<tr><td>Marshal/SimpleDocument/map-2</td><td>1.8x</td><td>2.7x</td></tr>
|
||||||
<tr><td>Marshal/SimpleDocument/struct-2</td><td>2.7x</td><td>3.8x</td></tr>
|
<tr><td>Marshal/SimpleDocument/struct-2</td><td>2.7x</td><td>3.8x</td></tr>
|
||||||
<tr><td>Unmarshal/SimpleDocument/map-2</td><td>3.8x</td><td>3.0x</td></tr>
|
<tr><td>Unmarshal/SimpleDocument/map-2</td><td>3.8x</td><td>3.0x</td></tr>
|
||||||
<tr><td>Unmarshal/SimpleDocument/struct-2</td><td>5.6x</td><td>4.1x</td></tr>
|
<tr><td>Unmarshal/SimpleDocument/struct-2</td><td>5.6x</td><td>4.1x</td></tr>
|
||||||
<tr><td>UnmarshalDataset/example-2</td><td>3.0x</td><td>3.2x</td></tr>
|
<tr><td>UnmarshalDataset/example-2</td><td>3.0x</td><td>3.2x</td></tr>
|
||||||
<tr><td>UnmarshalDataset/code-2</td><td>2.3x</td><td>2.9x</td></tr>
|
<tr><td>UnmarshalDataset/code-2</td><td>2.3x</td><td>2.9x</td></tr>
|
||||||
<tr><td>UnmarshalDataset/twitter-2</td><td>2.6x</td><td>2.7x</td></tr>
|
<tr><td>UnmarshalDataset/twitter-2</td><td>2.6x</td><td>2.7x</td></tr>
|
||||||
<tr><td>UnmarshalDataset/citm_catalog-2</td><td>2.2x</td><td>2.3x</td></tr>
|
<tr><td>UnmarshalDataset/citm_catalog-2</td><td>2.2x</td><td>2.3x</td></tr>
|
||||||
<tr><td>UnmarshalDataset/canada-2</td><td>1.8x</td><td>1.5x</td></tr>
|
<tr><td>UnmarshalDataset/canada-2</td><td>1.8x</td><td>1.5x</td></tr>
|
||||||
<tr><td>UnmarshalDataset/config-2</td><td>4.1x</td><td>2.9x</td></tr>
|
<tr><td>UnmarshalDataset/config-2</td><td>4.1x</td><td>2.9x</td></tr>
|
||||||
<tr><td>geomean</td><td>2.7x</td><td>2.8x</td></tr>
|
<tr><td>geomean</td><td>2.7x</td><td>2.8x</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>
|
<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>
|
||||||
</details>
|
</details>
|
||||||
@@ -233,24 +233,24 @@ Go-toml provides three handy command line tools:
|
|||||||
|
|
||||||
* `tomljson`: Reads a TOML file and outputs its JSON representation.
|
* `tomljson`: Reads a TOML file and outputs its JSON representation.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go install github.com/pelletier/go-toml/v2/cmd/tomljson@latest
|
$ go install github.com/pelletier/go-toml/v2/cmd/tomljson@latest
|
||||||
$ tomljson --help
|
$ tomljson --help
|
||||||
```
|
```
|
||||||
|
|
||||||
* `jsontoml`: Reads a JSON file and outputs a TOML representation.
|
* `jsontoml`: Reads a JSON file and outputs a TOML representation.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go install github.com/pelletier/go-toml/v2/cmd/jsontoml@latest
|
$ go install github.com/pelletier/go-toml/v2/cmd/jsontoml@latest
|
||||||
$ jsontoml --help
|
$ jsontoml --help
|
||||||
```
|
```
|
||||||
|
|
||||||
* `tomll`: Lints and reformats a TOML file.
|
* `tomll`: Lints and reformats a TOML file.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go install github.com/pelletier/go-toml/v2/cmd/tomll@latest
|
$ go install github.com/pelletier/go-toml/v2/cmd/tomll@latest
|
||||||
$ tomll --help
|
$ tomll --help
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker image
|
### Docker image
|
||||||
|
|
||||||
@@ -301,7 +301,7 @@ type doc struct {
|
|||||||
|
|
||||||
d := doc{
|
d := doc{
|
||||||
A: inner{
|
A: inner{
|
||||||
B: "Before",
|
B: "Before",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,10 +565,11 @@ complete solutions exist out there.
|
|||||||
|
|
||||||
## Versioning
|
## Versioning
|
||||||
|
|
||||||
Go-toml follows [Semantic Versioning](https://semver.org). The supported version
|
Expect for parts explicitely marked otherwise, go-toml follows [Semantic
|
||||||
of [TOML](https://github.com/toml-lang/toml) is indicated at the beginning of
|
Versioning](https://semver.org). The supported version of
|
||||||
this document. The last two major versions of Go are supported
|
[TOML](https://github.com/toml-lang/toml) is indicated at the beginning of this
|
||||||
(see [Go Release Policy](https://golang.org/doc/devel/release.html#policy)).
|
document. The last two major versions of Go are supported (see [Go Release
|
||||||
|
Policy](https://golang.org/doc/devel/release.html#policy)).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -19,6 +19,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
"github.com/pelletier/go-toml/v2"
|
||||||
@@ -33,7 +34,11 @@ Reading from a file:
|
|||||||
jsontoml file.json > file.toml
|
jsontoml file.json > file.toml
|
||||||
`
|
`
|
||||||
|
|
||||||
|
var useJsonNumber bool
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
flag.BoolVar(&useJsonNumber, "use-json-number", false, "unmarshal numbers into `json.Number` type instead of as `float64`")
|
||||||
|
|
||||||
p := cli.Program{
|
p := cli.Program{
|
||||||
Usage: usage,
|
Usage: usage,
|
||||||
Fn: convert,
|
Fn: convert,
|
||||||
@@ -45,11 +50,17 @@ func convert(r io.Reader, w io.Writer) error {
|
|||||||
var v interface{}
|
var v interface{}
|
||||||
|
|
||||||
d := json.NewDecoder(r)
|
d := json.NewDecoder(r)
|
||||||
|
e := toml.NewEncoder(w)
|
||||||
|
|
||||||
|
if useJsonNumber {
|
||||||
|
d.UseNumber()
|
||||||
|
e.SetMarshalJsonNumbers(true)
|
||||||
|
}
|
||||||
|
|
||||||
err := d.Decode(&v)
|
err := d.Decode(&v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e := toml.NewEncoder(w)
|
|
||||||
return e.Encode(v)
|
return e.Encode(v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ import (
|
|||||||
|
|
||||||
func TestConvert(t *testing.T) {
|
func TestConvert(t *testing.T) {
|
||||||
examples := []struct {
|
examples := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
expected string
|
expected string
|
||||||
errors bool
|
errors bool
|
||||||
|
useJsonNumber bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid json",
|
name: "valid json",
|
||||||
@@ -26,6 +27,19 @@ func TestConvert(t *testing.T) {
|
|||||||
}`,
|
}`,
|
||||||
expected: `[mytoml]
|
expected: `[mytoml]
|
||||||
a = 42.0
|
a = 42.0
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "use json number",
|
||||||
|
useJsonNumber: true,
|
||||||
|
input: `
|
||||||
|
{
|
||||||
|
"mytoml": {
|
||||||
|
"a": 42
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
expected: `[mytoml]
|
||||||
|
a = 42
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -37,6 +51,7 @@ a = 42.0
|
|||||||
|
|
||||||
for _, e := range examples {
|
for _, e := range examples {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
|
useJsonNumber = e.useJsonNumber
|
||||||
err := convert(strings.NewReader(e.input), b)
|
err := convert(strings.NewReader(e.input), b)
|
||||||
if e.errors {
|
if e.errors {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|||||||
+29
-4
@@ -3,6 +3,7 @@ package toml
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding"
|
"encoding"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
@@ -37,10 +38,11 @@ type Encoder struct {
|
|||||||
w io.Writer
|
w io.Writer
|
||||||
|
|
||||||
// global settings
|
// global settings
|
||||||
tablesInline bool
|
tablesInline bool
|
||||||
arraysMultiline bool
|
arraysMultiline bool
|
||||||
indentSymbol string
|
indentSymbol string
|
||||||
indentTables bool
|
indentTables bool
|
||||||
|
marshalJsonNumbers bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEncoder returns a new Encoder that writes to w.
|
// NewEncoder returns a new Encoder that writes to w.
|
||||||
@@ -87,6 +89,17 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
|
|||||||
return enc
|
return enc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetMarshalJsonNumbers forces the encoder to serialize `json.Number` as a
|
||||||
|
// float or integer instead of relying on TextMarshaler to emit a string.
|
||||||
|
//
|
||||||
|
// *Unstable:* This method does not follow the compatiblity guarnatees of
|
||||||
|
// semver. It can be changed or removed without a new major version being
|
||||||
|
// issued.
|
||||||
|
func (enc *Encoder) SetMarshalJsonNumbers(indent bool) *Encoder {
|
||||||
|
enc.marshalJsonNumbers = indent
|
||||||
|
return enc
|
||||||
|
}
|
||||||
|
|
||||||
// Encode writes a TOML representation of v to the stream.
|
// Encode writes a TOML representation of v to the stream.
|
||||||
//
|
//
|
||||||
// If v cannot be represented to TOML it returns an error.
|
// If v cannot be represented to TOML it returns an error.
|
||||||
@@ -252,6 +265,18 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e
|
|||||||
return append(b, x.String()...), nil
|
return append(b, x.String()...), nil
|
||||||
case LocalDateTime:
|
case LocalDateTime:
|
||||||
return append(b, x.String()...), nil
|
return append(b, x.String()...), nil
|
||||||
|
case json.Number:
|
||||||
|
if enc.marshalJsonNumbers {
|
||||||
|
if x == "" { /// Useful zero value.
|
||||||
|
return append(b, "0"...), nil
|
||||||
|
} else if v, err := x.Int64(); err == nil {
|
||||||
|
return enc.encode(b, ctx, reflect.ValueOf(v))
|
||||||
|
} else if f, err := x.Float64(); err == nil {
|
||||||
|
return enc.encode(b, ctx, reflect.ValueOf(f))
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("toml: unable to convert %q to int64 or float64", x)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasTextMarshaler := v.Type().Implements(textMarshalerType)
|
hasTextMarshaler := v.Type().Implements(textMarshalerType)
|
||||||
|
|||||||
@@ -948,6 +948,29 @@ func TestEncoderSetIndentSymbol(t *testing.T) {
|
|||||||
assert.Equal(t, expected, w.String())
|
assert.Equal(t, expected, w.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEncoderSetMarshalJsonNumbers(t *testing.T) {
|
||||||
|
var w strings.Builder
|
||||||
|
enc := toml.NewEncoder(&w)
|
||||||
|
enc.SetMarshalJsonNumbers(true)
|
||||||
|
err := enc.Encode(map[string]interface{}{
|
||||||
|
"A": json.Number("1.1"),
|
||||||
|
"B": json.Number("42e-3"),
|
||||||
|
"C": json.Number("42"),
|
||||||
|
"D": json.Number("0"),
|
||||||
|
"E": json.Number("0.0"),
|
||||||
|
"F": json.Number(""),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
expected := `A = 1.1
|
||||||
|
B = 0.042
|
||||||
|
C = 42
|
||||||
|
D = 0
|
||||||
|
E = 0.0
|
||||||
|
F = 0
|
||||||
|
`
|
||||||
|
assert.Equal(t, expected, w.String())
|
||||||
|
}
|
||||||
|
|
||||||
func TestEncoderOmitempty(t *testing.T) {
|
func TestEncoderOmitempty(t *testing.T) {
|
||||||
type doc struct {
|
type doc struct {
|
||||||
String string `toml:",omitempty,multiline"`
|
String string `toml:",omitempty,multiline"`
|
||||||
|
|||||||
Reference in New Issue
Block a user