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
Ashley Jeffs
committed
Apr 29, 2014
0 parents
commit e760438
Showing
4 changed files
with
370 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Copyright (c) 2014 Ashley Jeffs | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
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,106 @@ | ||
![Gabs](http://www.creepybit.co.uk/images/gabs_logo.png "Gabs") | ||
|
||
Gabs is a small utility for dealing with dynamic or unknown JSON structures in golang. It's pretty much just a helpful wrapper around the golang json.Marshal/json.Unmarshal behaviour and map[string]interface{} objects. | ||
|
||
https://godoc.org/github.com/Jeffail/gabs | ||
|
||
##How to install: | ||
|
||
```bash | ||
go get github.com/jeffail/gabs | ||
``` | ||
|
||
##How to use: | ||
|
||
```go | ||
... | ||
|
||
import "github.com/jeffail/gabs" | ||
|
||
jsonParsed, err := gabs.ParseJson([]byte(`{ | ||
"outter":{ | ||
"inner":{ | ||
"value1":10, | ||
"value2":22 | ||
}, | ||
"alsoInner":{ | ||
"value1":20 | ||
} | ||
} | ||
}`)) | ||
|
||
if err != nil { | ||
// You done goofed | ||
} | ||
|
||
/* Search returns an object of the same type as jsonParsed which should contain the target | ||
* data. Data returns the interface{} wrapped target object, it's then safe to attempt to cast | ||
* this object in order to determine whether the search obtained what you expected. | ||
*/ | ||
if valueOne, ok := jsonParsed.Search("outter", "inner", "value1").Data().(float64); ok { | ||
// outter.inner.value1 was found and its value is now stored in valueOne. | ||
} else { | ||
// outter.inner.value1 was either non-existant in the JSON structure or was of a different type. | ||
} | ||
|
||
if err := jsonParsed.Set(10, "outter", "inner", "value2"); err == nil { | ||
// outter.inner.value2 was found and has been set to 10. | ||
} else { | ||
// outter.inner.value2 was not found in the JSON structure. | ||
} | ||
|
||
... | ||
``` | ||
|
||
Doing things like merging different JSON structures is also fairly simple. | ||
|
||
```go | ||
... | ||
|
||
import "github.com/jeffail/gabs" | ||
|
||
json1, _ := gabs.ParseJson([]byte(`{ | ||
"languages":{ | ||
"english":{ | ||
"places":0 | ||
}, | ||
"french": { | ||
"places": [ | ||
"france", | ||
"belgium" | ||
] | ||
} | ||
} | ||
}`)) | ||
|
||
json2, _ := gabs.ParseJson([]byte(`{ | ||
"places":[ | ||
"great_britain", | ||
"united_states_of_america", | ||
"the_world" | ||
] | ||
}`)) | ||
|
||
if english_places := json2.Search("places").Data(); english_places != nil { | ||
json1.Set(english_places, "languages", "english", "places") | ||
} | ||
|
||
/* If all went well then the structure of json1 should now be: | ||
"languages":{ | ||
"english":{ | ||
"places":[ | ||
"great_britain", | ||
"united_states_of_america", | ||
"the_world" | ||
] | ||
}, | ||
"french": { | ||
"places": [ | ||
"france", | ||
"belgium" | ||
] | ||
} | ||
*/ | ||
|
||
... | ||
``` |
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,114 @@ | ||
/* | ||
Copyright (c) 2014 Ashley Jeffs | ||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. | ||
*/ | ||
|
||
// Package gabs implements a simplified wrapper around json parsing an unknown structure | ||
package gabs | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"io/ioutil" | ||
) | ||
|
||
/* | ||
Container - an internal structure that holds a reference to the core interface map of the parsed | ||
json. Use this container to move context. | ||
*/ | ||
type Container struct { | ||
object interface{} | ||
} | ||
|
||
/* | ||
Search - Attempt to find and return an object within the JSON structure by specifying the hierarchy of | ||
field names to locate the target. | ||
*/ | ||
func (g *Container) Search(hierarchy ...string) *Container { | ||
var object interface{} | ||
|
||
object = g.object | ||
for target := 0; target < len(hierarchy); target++ { | ||
if mmap, ok := object.(map[string]interface{}); ok { | ||
object = mmap[hierarchy[target]] | ||
} else { | ||
return &Container{nil} | ||
} | ||
} | ||
|
||
return &Container{object} | ||
} | ||
|
||
/* | ||
Data - Return the contained data as an interface{}. | ||
*/ | ||
func (g *Container) Data() interface{} { | ||
return g.object | ||
} | ||
|
||
/* | ||
Set - Set the value for an object within the JSON structure by specifying the new value and the | ||
hierarchy of field names to locate the target. | ||
*/ | ||
func (g *Container) Set(value interface{}, hierarchy ...string) error { | ||
parent := g.Search(hierarchy[:len(hierarchy)-1]...).Data() | ||
|
||
if mmap, ok := parent.(map[string]interface{}); ok { | ||
mmap[hierarchy[len(hierarchy)-1]] = value | ||
} else { | ||
return errors.New("target object was not found in structure") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
/* | ||
ParseJson - Convert a string into a representation of the parsed JSON. | ||
*/ | ||
func ParseJson(sample []byte) (*Container, error) { | ||
var gabs Container | ||
|
||
if err := json.Unmarshal(sample, &gabs.object); err != nil { | ||
return nil, err | ||
} | ||
|
||
if _, ok := gabs.object.(map[string]interface{}); ok { | ||
return &gabs, nil | ||
} | ||
return nil, errors.New("json appears to contain no data.") | ||
} | ||
|
||
/* | ||
ParseJsonFile - Read a file and convert into a representation of the parsed JSON. | ||
*/ | ||
func ParseJsonFile(path string) (*Container, error) { | ||
if len(path) > 0 { | ||
if cBytes, err := ioutil.ReadFile(path); err == nil { | ||
if container, err := ParseJson(cBytes); err == nil { | ||
return container, nil | ||
} else { | ||
return nil, err | ||
} | ||
} else { | ||
return nil, err | ||
} | ||
} | ||
return nil, errors.New("file path was invalid") | ||
} |
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,131 @@ | ||
package gabs | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestBasic(t *testing.T) { | ||
sample := []byte(`{"test":{"value":10},"test2":20}`) | ||
|
||
val, err := ParseJson(sample) | ||
if err != nil { | ||
t.Errorf("Failed to parse: %v", err) | ||
return | ||
} | ||
|
||
if result, ok := val.Search([]string{"test", "value"}...).Data().(float64); ok { | ||
if result != 10 { | ||
t.Errorf("Wrong value of result: %v", result) | ||
} | ||
} else { | ||
t.Errorf("Didn't find test.value") | ||
} | ||
|
||
if _, ok := val.Search("test2", "value").Data().(string); ok { | ||
t.Errorf("Somehow found a field that shouldn't exist") | ||
} | ||
|
||
if result, ok := val.Search("test2").Data().(float64); ok { | ||
if result != 20 { | ||
t.Errorf("Wrong value of result: %v", result) | ||
} | ||
} else { | ||
t.Errorf("Didn't find test2") | ||
} | ||
} | ||
|
||
func TestModify(t *testing.T) { | ||
sample := []byte(`{"test":{"value":10},"test2":20}`) | ||
|
||
val, err := ParseJson(sample) | ||
if err != nil { | ||
t.Errorf("Failed to parse: %v", err) | ||
return | ||
} | ||
|
||
if err := val.Set(45.0, "test", "value"); err != nil { | ||
t.Errorf("Failed to set field") | ||
} | ||
|
||
if result, ok := val.Search([]string{"test", "value"}...).Data().(float64); ok { | ||
if result != 45 { | ||
t.Errorf("Wrong value of result: %v", result) | ||
} | ||
} else { | ||
t.Errorf("Didn't find test.value") | ||
} | ||
} | ||
|
||
func TestArrays(t *testing.T) { | ||
json1, _ := ParseJson([]byte(`{ | ||
"languages":{ | ||
"english":{ | ||
"places":0 | ||
}, | ||
"french": { | ||
"places": [ | ||
"france", | ||
"belgium" | ||
] | ||
} | ||
} | ||
}`)) | ||
|
||
json2, _ := ParseJson([]byte(`{ | ||
"places":[ | ||
"great_britain", | ||
"united_states_of_america", | ||
"the_world" | ||
] | ||
}`)) | ||
|
||
if english_places := json2.Search("places").Data(); english_places != nil { | ||
json1.Set(english_places, "languages", "english", "places") | ||
} else { | ||
t.Errorf("Didn't find places in json2") | ||
} | ||
|
||
if english_places := json1.Search("languages", "english", "places").Data(); english_places != nil { | ||
|
||
english_array, ok := english_places.([]interface{}) | ||
if !ok { | ||
t.Errorf("places in json1 (%v) was not an array", english_places) | ||
} | ||
|
||
if len(english_array) != 3 { | ||
t.Errorf("wrong length of array: %v", len(english_array)) | ||
} | ||
|
||
} else { | ||
t.Errorf("Didn't find places in json1") | ||
} | ||
} | ||
|
||
func TestLargeSample(t *testing.T) { | ||
sample := []byte(`{ | ||
"test":{ | ||
"innerTest":{ | ||
"value":10, | ||
"value2":22, | ||
"value3":{ | ||
"moreValue":45 | ||
} | ||
} | ||
}, | ||
"test2":20 | ||
}`) | ||
|
||
val, err := ParseJson(sample) | ||
if err != nil { | ||
t.Errorf("Failed to parse: %v", err) | ||
return | ||
} | ||
|
||
if result, ok := val.Search("test", "innerTest", "value3", "moreValue").Data().(float64); ok { | ||
if result != 45 { | ||
t.Errorf("Wrong value of result: %v", result) | ||
} | ||
} else { | ||
t.Errorf("Didn't find value") | ||
} | ||
} |