-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
79cbf05
commit e1183d4
Showing
9 changed files
with
464 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package jsonnode | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
type Array []Node | ||
|
||
func (_ *Array) sealed() {} | ||
|
||
func (a *Array) String() string { | ||
var stringArray []string | ||
for _, node := range *a { | ||
stringArray = append(stringArray, node.String()) | ||
} | ||
return "Array [" + strings.Join(stringArray, ", ") + "]" | ||
} | ||
|
||
func (a *Array) decodeJSON(startToken json.Token, decoder *json.Decoder) error { | ||
if startToken != json.Delim('[') { | ||
return errors.New(fmt.Sprintf("Token is not the start of an array! Token: %v", startToken)) | ||
} | ||
for { // inside the array | ||
token, err := decoder.Token() | ||
if err != nil { | ||
return err | ||
} | ||
if token == json.Delim(']') { | ||
return nil | ||
} | ||
node, err := decodeNode(token, decoder) | ||
if err != nil { | ||
return err | ||
} | ||
*a = append(*a, node) | ||
} | ||
} | ||
|
||
func (a *Array) UnmarshalJSON(data []byte) error { | ||
d := createDecoder(bytes.NewReader(data)) | ||
|
||
var token, err = d.Token() | ||
if err != nil { | ||
return err | ||
} | ||
return a.decodeJSON(token, d) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package jsonnode | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
) | ||
|
||
func createDecoder(r io.Reader) *json.Decoder { | ||
d := json.NewDecoder(r) | ||
d.UseNumber() | ||
return d | ||
} | ||
|
||
func decodeNode(token json.Token, decoder *json.Decoder) (Node, error) { | ||
{ // test if it's an array | ||
var nestedArray = Array{} | ||
err := nestedArray.decodeJSON(token, decoder) | ||
if err == nil { | ||
return &nestedArray, nil | ||
} | ||
} | ||
{ // test if it's an object | ||
var nestedObject = Object{} | ||
err := nestedObject.decodeJSON(token, decoder) | ||
if err == nil { | ||
return &nestedObject, nil | ||
} | ||
} | ||
primitiveNode := parsePrimitive(token) | ||
if primitiveNode != nil { | ||
return primitiveNode, nil | ||
} | ||
return nil, errors.New(fmt.Sprintf("Unknown token: %v", token)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package jsonnode | ||
|
||
import ( | ||
"encoding/json" | ||
"testing" | ||
) | ||
|
||
func TestPrimitivesAndNestedArray(t *testing.T) { | ||
jsonString := ` | ||
[ | ||
true, | ||
"asdf", | ||
null, | ||
1.1, | ||
[1, 2] | ||
] | ||
` | ||
var array = Array{} | ||
//err := array.UnmarshalJSON([]byte(jsonString)) | ||
err := json.Unmarshal([]byte(jsonString), &array) | ||
if err != nil { | ||
t.Fatal("Could not unmarshal JSON", err) | ||
return | ||
} | ||
if len(array) == 0 { | ||
t.Error("Empty array") | ||
return | ||
} | ||
if value, ok := array[0].(*Boolean); !ok || *value != true { | ||
if !ok { | ||
t.Error("First element is not a boolean") | ||
return | ||
} | ||
t.Error("First element's value is not true!") | ||
return | ||
} | ||
if value, ok := array[1].(*String); !ok || *value != "asdf" { | ||
t.Error("Second element's value is not asdf!") | ||
return | ||
} | ||
if value, ok := array[2].(Null); !ok || value != NULL { | ||
t.Error("Third element's value is not null!") | ||
return | ||
} | ||
if value, ok := array[3].(*Number); !ok || value.Number() != "1.1" { | ||
t.Error("Forth element's value is not 1.1!") | ||
return | ||
} | ||
{ | ||
nestedArray, ok := array[4].(*Array) | ||
if !ok { | ||
t.Error("Fifth element is not an array!") | ||
return | ||
} | ||
if len(*nestedArray) != 2 { | ||
t.Error("Incorrect data") | ||
return | ||
} | ||
if value, ok := (*nestedArray)[0].(*Number); !ok || value.Number() != "1" { | ||
t.Error("Incorrect data") | ||
return | ||
} | ||
if value, ok := (*nestedArray)[1].(*Number); !ok || value.Number() != "2" { | ||
t.Error("Incorrect data") | ||
return | ||
} | ||
} | ||
} | ||
func TestObject(t *testing.T) { | ||
jsonString := ` | ||
{ | ||
"a": 1, | ||
"b": 2, | ||
"c": 3 | ||
} | ||
` | ||
var object = NewObject() | ||
err := json.Unmarshal([]byte(jsonString), &object) | ||
if err != nil { | ||
t.Fatal("Could not unmarshal JSON", err) | ||
return | ||
} | ||
if len(object.Keys()) != 3 { | ||
t.Error("Incorrect keys length") | ||
return | ||
} | ||
{ | ||
nodeValue := object.Get("a") | ||
if nodeValue == nil { | ||
t.Error("No value for key a") | ||
return | ||
} | ||
number, ok := nodeValue.(*Number) | ||
if !ok { | ||
t.Error("Value is not a number") | ||
return | ||
} | ||
value, err := number.Int() | ||
if err != nil { | ||
t.Fatal(err) | ||
return | ||
} | ||
if value != 1 { | ||
t.Error("Value is incorrect") | ||
} | ||
} | ||
{ | ||
nodeValue := object.Get("b") | ||
if nodeValue == nil { | ||
t.Error("No value for key b") | ||
return | ||
} | ||
number, ok := nodeValue.(*Number) | ||
if !ok { | ||
t.Error("Value is not a number") | ||
return | ||
} | ||
value, err := number.Int() | ||
if err != nil { | ||
t.Fatal(err) | ||
return | ||
} | ||
if value != 2 { | ||
t.Error("Value is incorrect") | ||
} | ||
} | ||
{ | ||
nodeValue := object.Get("c") | ||
if nodeValue == nil { | ||
t.Error("No value for key c") | ||
return | ||
} | ||
number, ok := nodeValue.(*Number) | ||
if !ok { | ||
t.Error("Value is not a number") | ||
return | ||
} | ||
value, err := number.Int() | ||
if err != nil { | ||
t.Fatal(err) | ||
return | ||
} | ||
if value != 3 { | ||
t.Error("Value is incorrect") | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package jsonnode | ||
|
||
type Node interface { | ||
sealed() // make this a sealed interface by having unexported methods | ||
|
||
String() string | ||
// json.Unmarshaler | ||
UnmarshalJSON(data []byte) error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package jsonnode | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
type Object struct { | ||
data map[string]Node | ||
order []string | ||
} | ||
|
||
func NewObject() *Object { | ||
object := Object{ | ||
data: map[string]Node{}, | ||
} | ||
return &object | ||
} | ||
|
||
func (o *Object) decodeJSON(startToken json.Token, decoder *json.Decoder) error { | ||
if startToken != json.Delim('{') { | ||
return errors.New(fmt.Sprintf("Token is not the start of an object! Token: %v", startToken)) | ||
} | ||
for { | ||
|
||
keyToken, err := decoder.Token() | ||
if err != nil { | ||
return err | ||
} | ||
if keyToken == json.Delim('}') { | ||
return nil | ||
} | ||
var key string | ||
switch typedToken := keyToken.(type) { | ||
case string: | ||
key = typedToken | ||
default: | ||
return errors.New(fmt.Sprintf("invalid token for key to object. token: %v", keyToken)) | ||
} | ||
|
||
token, err := decoder.Token() | ||
if err != nil { | ||
return err | ||
} | ||
valueNode, err := decodeNode(token, decoder) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, keyExists := o.data[key] | ||
if !keyExists { | ||
o.order = append(o.order, key) | ||
} | ||
o.data[key] = valueNode | ||
} | ||
} | ||
|
||
func (o *Object) UnmarshalJSON(data []byte) error { | ||
d := createDecoder(bytes.NewReader(data)) | ||
|
||
var token, err = d.Token() | ||
if err != nil { | ||
return err | ||
} | ||
return o.decodeJSON(token, d) | ||
} | ||
|
||
func (_ *Object) sealed() {} | ||
|
||
func (o *Object) String() string { | ||
var result []string | ||
for _, key := range o.Keys() { | ||
value := o.Get(key) | ||
result = append(result, fmt.Sprintf("%s : %s", key, value)) | ||
} | ||
return strings.Join(result, ", ") | ||
} | ||
|
||
func (o *Object) Keys() []string { | ||
return o.order | ||
} | ||
func (o *Object) Get(key string) Node { | ||
node, exists := o.data[key] | ||
if !exists { | ||
return nil | ||
} | ||
return node | ||
} |
Oops, something went wrong.