Skip to content

Commit

Permalink
Use json.Number when decoding JSON.
Browse files Browse the repository at this point in the history
  • Loading branch information
dbohdan committed Oct 6, 2014
1 parent 12ed1d9 commit b15717b
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 29 deletions.
76 changes: 66 additions & 10 deletions README.md
@@ -1,3 +1,5 @@
# remarshal

Convert between TOML, YAML and JSON. When installed provides the command line
commands `toml2yaml`, `toml2json`, `yaml2toml`, `yaml2json`. `json2toml` and
`json2yaml` for format conversion as well as `toml2toml`, `yaml2yaml` and
Expand All @@ -11,25 +13,25 @@ remarshal -if inputformat -of outputformat [-indent-json=(true|false)]
```

where `inputformat` and `outputformat` can each be one of `toml`, `yaml` and
where `inputformat` and `outputformat` can each be `toml`, `yaml` or
`json`.

```
toml2yaml [-o outputfile] [[-i] inputfile]
toml2json [-indent-json=(true|false)] [-o outputfile] [[-i] inputfile]
toml2toml [-o outputfile] [[-i] inputfile]
yaml2toml [-o outputfile] [[-i] inputfile]
yaml2json [-indent-json=(true|false)] [-o outputfile] [[-i] inputfile]
json2toml [-o outputfile] [[-i] inputfile]
json2yaml [-o outputfile] [[-i] inputfile]
toml2toml [-o outputfile] [[-i] inputfile]
toml2yaml [-o outputfile] [[-i] inputfile]
yaml2yaml [-o outputfile] [[-i] inputfile]
json2yaml [-o outputfile] [[-i] inputfile]
toml2json [-indent-json=(true|false)] [-o outputfile] [[-i] inputfile]
yaml2json [-indent-json=(true|false)] [-o outputfile] [[-i] inputfile]
json2json [-indent-json=(true|false)] [-o outputfile] [[-i] inputfile]
```

The all of the above commands exit with status 0 on success and 1 on failure.
The all of the commands above exit with status 0 on success and 1 on failure.

If `inputfile` is not given or is `-` or a blank string the data to convert is
read from standard input. If `outputfile` is not given or is `-` or a blank
If no `inputfile` is given or it is `-` or a blank string the data to convert is
read from standard input. If no `outputfile` is given or it is `-` or a blank
string the result of the conversion is written to standard output.

For short commands (`x2y`) the flag `-i` before `inputfile` can be omitted if
Expand All @@ -50,7 +52,7 @@ sh tests.sh
sudo sh install.sh # install into /usr/local/bin
```

# Example
# Examples

```
$ ./remarshal -i example.toml -if toml -of yaml
Expand Down Expand Up @@ -78,16 +80,70 @@ owner:
dob: 1979-05-27T07:32:00Z
name: Tom Preston-Werner
organization: GitHub
products:
- name: Hammer
sku: 738594937
- color: gray
name: Nail
sku: 284758393
servers:
alpha:
dc: eqdc10
ip: 10.0.0.1
beta:
country: 中国
dc: eqdc10
ip: 10.0.0.2
title: TOML Example
$ curl -s http://api.openweathermap.org/data/2.5/weather\?q\=Kiev,ua | \
./remarshal -if json -of toml
base = "cmc stations"
cod = 200
dt = 1412532000
id = 703448
name = "Kiev"
[clouds]
all = 44
[coord]
lat = 50.43
lon = 30.52
[main]
humidity = 66
pressure = 1026
temp = 283.49
temp_max = 284.15
temp_min = 283.15
[sys]
country = "UA"
id = 7358
message = 0.2437
sunrise = 1412481902
sunset = 1412522846
type = 1
[[weather]]
description = "scattered clouds"
icon = "03n"
id = 802
main = "Clouds"
[wind]
deg = 80
speed = 2
```

# Known bugs

* Converting data with floating point values to YAML may cause the loss of
precision.

# License

MIT. See the file `LICENSE`.

`example.toml` from <https://github.com/toml-lang/toml>.
2 changes: 1 addition & 1 deletion VERSION
@@ -1 +1 @@
0.2.2
0.2.3
12 changes: 12 additions & 0 deletions example.toml
Expand Up @@ -24,6 +24,7 @@ enabled = true
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
country = "中国" # This should be parsed as UTF-8

[clients]
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
Expand All @@ -33,3 +34,14 @@ hosts = [
"alpha",
"omega"
]

# Products

[[products]]
name = "Hammer"
sku = 738594937

[[products]]
name = "Nail"
sku = 284758393
color = "gray"
87 changes: 69 additions & 18 deletions remarshal.go
Expand Up @@ -27,26 +27,73 @@ const (
fUnknown
)

// convertToStringMap recursively converts map[interface{}]interface{} to
// map[string]interface{}. This is needed before the TOML and JSON encoders can
// accept the data returned by the YAML decoder.
func convertToStringMap(m map[interface{}]interface{}) (
res map[string]interface{}, err error) {
res = make(map[string]interface{})
for k, v := range m {
switch v.(type) {
case map[interface{}]interface{}:
var err error
res[k.(string)], err =
convertToStringMap(v.(map[interface{}]interface{}))
// convertMapsToStringMaps recursively converts values of type
// map[interface{}]interface{} contained in item to map[string]interface{}. This
// is needed before the encoders for TOML and JSON can accept data returned by
// the YAML decoder.
func convertMapsToStringMaps(item interface{}) (res interface{}, err error) {
switch item.(type) {
case map[interface{}]interface{}:
res := make(map[string]interface{})
for k, v := range item.(map[interface{}]interface{}) {
res[k.(string)], err = convertMapsToStringMaps(v)
if err != nil {
return nil, err
}
default:
res[k.(string)] = v
}
return res, nil
case []interface{}:
res := make([]interface{}, len(item.([]interface{})))
for i, v := range item.([]interface{}) {
res[i], err = convertMapsToStringMaps(v)
if err != nil {
return nil, err
}
}
return res, nil
default:
return item, nil
}
}

// convertNumberToInt64 recursively walks the structures contained in item
// converting values of the type json.Number to int64 or, failing that, float64.
// This approach is meant to prevent encoders putting numbers stored as
// json.Number in quotes or encoding large intergers in scientific notation.
func convertNumberToInt64(item interface{}) (res interface{}, err error) {
switch item.(type) {
case map[string]interface{}:
res := make(map[string]interface{})
for k, v := range item.(map[string]interface{}) {
res[k], err = convertNumberToInt64(v)
if err != nil {
return nil, err
}
}
return res, nil
case []interface{}:
res := make([]interface{}, len(item.([]interface{})))
for i, v := range item.([]interface{}) {
res[i], err = convertNumberToInt64(v)
if err != nil {
return nil, err
}
}
return res, nil
case json.Number:
n, err := item.(json.Number).Int64()
if err != nil {
f, err := item.(json.Number).Float64()
if err != nil {
// Can't convert to Int64.
return item, nil
}
return f, nil
}
return n, nil
default:
return item, nil
}
return
}

func stringToFormat(s string) (f format, err error) {
Expand Down Expand Up @@ -97,11 +144,15 @@ func remarshal(input []byte, inputFormat format, outputFormat format,
case fYAML:
err = yaml.Unmarshal(input, &data)
if err == nil {
data, err = convertToStringMap(
data.(map[interface{}]interface{}))
data, err = convertMapsToStringMaps(data)
}
case fJSON:
err = json.Unmarshal(input, &data)
decoder := json.NewDecoder(bytes.NewReader(input))
decoder.UseNumber()
err = decoder.Decode(&data)
if err == nil {
data, err = convertNumberToInt64(data)
}
}
if err != nil {
return nil, err
Expand Down

0 comments on commit b15717b

Please sign in to comment.