Skip to content

Commit

Permalink
use json.Number internally, to handle both int64 and float64
Browse files Browse the repository at this point in the history
This defers the actual parsing of the text value to the numeric type
until you dereference it using the Int(), Int64() or Float64()
receiver methods.

This has the benefit that large integers (>= 1e6) are not marshaled in
floating point exponent notation, and that very large integers (>=
2^53) do not lose precision when marshaled because they were stored
internally as float64s.

This retains backward compatibility with Go 1.0.x because it doesn't
have json.Number.
  • Loading branch information
joeshaw authored and mreiferson committed Oct 2, 2013
1 parent 9002e63 commit 7f4699f
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 51 deletions.
31 changes: 0 additions & 31 deletions simplejson.go
Expand Up @@ -31,11 +31,6 @@ func (j *Json) Encode() ([]byte, error) {
return j.MarshalJSON()
}

// Implements the json.Unmarshaler interface.
func (j *Json) UnmarshalJSON(p []byte) error {
return json.Unmarshal(p, &j.data)
}

// Implements the json.Marshaler interface.
func (j *Json) MarshalJSON() ([]byte, error) {
return json.Marshal(&j.data)
Expand Down Expand Up @@ -151,32 +146,6 @@ func (j *Json) String() (string, error) {
return "", errors.New("type assertion to string failed")
}

// Float64 type asserts to `float64`
func (j *Json) Float64() (float64, error) {
if i, ok := (j.data).(float64); ok {
return i, nil
}
return -1, errors.New("type assertion to float64 failed")
}

// Int type asserts to `float64` then converts to `int`
func (j *Json) Int() (int, error) {
if f, ok := (j.data).(float64); ok {
return int(f), nil
}

return -1, errors.New("type assertion to float64 failed")
}

// Int type asserts to `float64` then converts to `int64`
func (j *Json) Int64() (int64, error) {
if f, ok := (j.data).(float64); ok {
return int64(f), nil
}

return -1, errors.New("type assertion to float64 failed")
}

// Bytes type asserts to `[]byte`
func (j *Json) Bytes() ([]byte, error) {
if s, ok := (j.data).(string); ok {
Expand Down
39 changes: 39 additions & 0 deletions simplejson_go10.go
@@ -0,0 +1,39 @@
// +build !go1.1

package simplejson

import (
"encoding/json"
"errors"
)

// Implements the json.Unmarshaler interface.
func (j *Json) UnmarshalJSON(p []byte) error {
return json.Unmarshal(p, &j.data)
}

// Float64 type asserts to `float64`
func (j *Json) Float64() (float64, error) {
if i, ok := (j.data).(float64); ok {
return i, nil
}
return -1, errors.New("type assertion to float64 failed")
}

// Int type asserts to `float64` then converts to `int`
func (j *Json) Int() (int, error) {
if f, ok := (j.data).(float64); ok {
return int(f), nil
}

return -1, errors.New("type assertion to float64 failed")
}

// Int type asserts to `float64` then converts to `int64`
func (j *Json) Int64() (int64, error) {
if f, ok := (j.data).(float64); ok {
return int64(f), nil
}

return -1, errors.New("type assertion to float64 failed")
}
45 changes: 45 additions & 0 deletions simplejson_go10_test.go
@@ -0,0 +1,45 @@
// +build !go1.1

package simplejson

import (
"github.com/bmizerany/assert"
"io/ioutil"
"log"
"strconv"
"testing"
)

func TestSimplejsonGo10(t *testing.T) {
log.SetOutput(ioutil.Discard)

js, err := NewJson([]byte(`{
"test": {
"array": [1, "2", 3],
"arraywithsubs": [{"subkeyone": 1},
{"subkeytwo": 2, "subkeythree": 3}]
}
}`))

assert.NotEqual(t, nil, js)
assert.Equal(t, nil, err)

arr, _ := js.Get("test").Get("array").Array()
assert.NotEqual(t, nil, arr)
for i, v := range arr {
var iv int
switch v.(type) {
case float64:
iv = int(v.(float64))
case string:
iv, _ = strconv.Atoi(v.(string))
}
assert.Equal(t, i+1, iv)
}

ma := js.Get("test").Get("array").MustArray()
assert.Equal(t, ma, []interface{}{float64(1), "2", float64(3)})

mm := js.Get("test").Get("arraywithsubs").GetIndex(0).MustMap()
assert.Equal(t, mm, map[string]interface{}{"subkeyone": float64(1)})
}
43 changes: 43 additions & 0 deletions simplejson_go11.go
@@ -0,0 +1,43 @@
// +build go1.1

package simplejson

import (
"bytes"
"encoding/json"
"errors"
)

// Implements the json.Unmarshaler interface.
func (j *Json) UnmarshalJSON(p []byte) error {
dec := json.NewDecoder(bytes.NewBuffer(p))
dec.UseNumber()
return dec.Decode(&j.data)
}

// Float64 type asserts to `json.Number` then converts to `float64`
func (j *Json) Float64() (float64, error) {
if n, ok := (j.data).(json.Number); ok {
return n.Float64()
}
return -1, errors.New("type assertion to float64 failed")
}

// Int type asserts to `json.Number` then converts to `int`
func (j *Json) Int() (int, error) {
if n, ok := (j.data).(json.Number); ok {
i, ok := n.Int64()
return int(i), ok
}

return -1, errors.New("type assertion to float64 failed")
}

// Int type asserts to `json.Number` then converts to `int64`
func (j *Json) Int64() (int64, error) {
if n, ok := (j.data).(json.Number); ok {
return n.Int64()
}

return -1, errors.New("type assertion to float64 failed")
}
48 changes: 48 additions & 0 deletions simplejson_go11_test.go
@@ -0,0 +1,48 @@
// +build go1.1

package simplejson

import (
"encoding/json"
"github.com/bmizerany/assert"
"io/ioutil"
"log"
"strconv"
"testing"
)

func TestSimplejsonGo11(t *testing.T) {
log.SetOutput(ioutil.Discard)

js, err := NewJson([]byte(`{
"test": {
"array": [1, "2", 3],
"arraywithsubs": [{"subkeyone": 1},
{"subkeytwo": 2, "subkeythree": 3}]
}
}`))

assert.NotEqual(t, nil, js)
assert.Equal(t, nil, err)

arr, _ := js.Get("test").Get("array").Array()
assert.NotEqual(t, nil, arr)
for i, v := range arr {
var iv int
switch v.(type) {
case json.Number:
i64, err := v.(json.Number).Int64()
assert.Equal(t, nil, err)
iv = int(i64)
case string:
iv, _ = strconv.Atoi(v.(string))
}
assert.Equal(t, i+1, iv)
}

ma := js.Get("test").Get("array").MustArray()
assert.Equal(t, ma, []interface{}{json.Number("1"), "2", json.Number("3")})

mm := js.Get("test").Get("arraywithsubs").GetIndex(0).MustMap()
assert.Equal(t, mm, map[string]interface{}{"subkeyone": json.Number("1")})
}
20 changes: 0 additions & 20 deletions simplejson_test.go
Expand Up @@ -5,7 +5,6 @@ import (
"github.com/bmizerany/assert"
"io/ioutil"
"log"
"strconv"
"testing"
)

Expand Down Expand Up @@ -38,19 +37,6 @@ func TestSimplejson(t *testing.T) {
_, ok = js.CheckGet("missing_key")
assert.Equal(t, false, ok)

arr, _ := js.Get("test").Get("array").Array()
assert.NotEqual(t, nil, arr)
for i, v := range arr {
var iv int
switch v.(type) {
case float64:
iv = int(v.(float64))
case string:
iv, _ = strconv.Atoi(v.(string))
}
assert.Equal(t, i+1, iv)
}

aws := js.Get("test").Get("arraywithsubs")
assert.NotEqual(t, nil, aws)
var awsval int
Expand Down Expand Up @@ -85,15 +71,9 @@ func TestSimplejson(t *testing.T) {
ms2 := js.Get("test").Get("missing_string").MustString("fyea")
assert.Equal(t, "fyea", ms2)

ma := js.Get("test").Get("array").MustArray()
assert.Equal(t, ma, []interface{}{float64(1), "2", float64(3)})

ma2 := js.Get("test").Get("missing_array").MustArray([]interface{}{"1", 2, "3"})
assert.Equal(t, ma2, []interface{}{"1", 2, "3"})

mm := js.Get("test").Get("arraywithsubs").GetIndex(0).MustMap()
assert.Equal(t, mm, map[string]interface{}{"subkeyone": float64(1)})

mm2 := js.Get("test").Get("missing_map").MustMap(map[string]interface{}{"found": false})
assert.Equal(t, mm2, map[string]interface{}{"found": false})

Expand Down

0 comments on commit 7f4699f

Please sign in to comment.