Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashley Jeffs committed Apr 29, 2014
0 parents commit e760438
Show file tree
Hide file tree
Showing 4 changed files with 370 additions and 0 deletions.
19 changes: 19 additions & 0 deletions LICENSE
@@ -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.
106 changes: 106 additions & 0 deletions README.md
@@ -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"
]
}
*/

...
```
114 changes: 114 additions & 0 deletions gabs.go
@@ -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")
}
131 changes: 131 additions & 0 deletions gabs_test.go
@@ -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")
}
}

0 comments on commit e760438

Please sign in to comment.