- Flexibale constructor
- Safe (no
panic()
) access to keys of deeply nested JSONs (including arrays) - value getters return a struct
{Value, IsValid}
instead of multiple return params for easier inlining (Value
defaulting to a sensible default) - Implementing
sql.Scanner
&sql.Valuer
for easy integration with JSON columns - Cute
jsn.Map
wrapper tomap[string]interface{}
for composition of arbiterary JSON objects - Easier iteration over JSON arrays
- Various other helper methods for happier life
import "github.com/michael-go/go-jsn/jsn"
jsn.Json
represents any valid JSON including: map, array, bool, number, string, or null.
a Json
can be constructed in several ways:
- via
jsn.NewJson()
accepting:- a JSON string from a
string
,[]byte
,io.Reader
- any
interface{}
that isjson.Marshal
-able
- a JSON string from a
j, err := jsn.NewJson(`{"go": 1}`)
func handler(w http.ResponseWriter, r *http.Request) {
j, err := jsn.NewJson(r.Body) // io.Reader
}
type User struct {
Name string
Email string
}
j, err = jsn.NewJson(Pixel{"Gopher", "gopher@golang.org"})
- scanned from a JSON database column (works for both string and
jsonb
):
var email string
var prefs jsn.Json
err := db.QueryRow(`SELECT email, prefs FROM users`).Scan(&email, &prefs)
- via
json.Umarshal()
:
err = json.Unmarshal([]byte(`{}`), &j)
- via
jsn.Map
:
j = jsn.Map{"time": time.Now()}.Json()
any sub-element (map value or array element) of a Json
is a Json
.
- use
Get(key string)
or it's shortcutK(key string)
to get a map sub-element by key - use
I(index int)
to get an array sub-element by index - calling the above methods on a non map/array element will just return an empty Json which is basically equivalent to Javascipt's
undefined
, but here you can safely call it's methods withoutpanic
-ing ornull
-dereferencing - to get the actual value of a leaf element, use one of
Json
's value methodsString()
,Int()
,Int64()
,Float64()
,Bool()
depending on the expected type. Each returns a struct with the typedValue
and anIsValid
field that will be false if the actual type is different or if theJson
object itself is "undefined"
an example:
j, err := jsn.NewJson(`{
"go": {
"pher": [10, "koko", {
"lang": 20,
"name": "gogo"
}],
"path": "/home"
}
}`)
if err != nil {
panic(err)
}
// dive through keys & array indexes:
fmt.Println(j.K("go").K("pher").I(2).K("name").String().Value)
// => gogo
// asking for the wrong type:
fmt.Println(j.K("go").K("path").Bool().Value)
// => false (deafult value for bool)
fmt.Printf("%#v\n", j.K("no").K("pher").Bool())
// => jsn.Bool{Value:false, IsValid:false}
// going into a non-existing path
fmt.Println(j.K("no").K("pher").Int().Value)
// => 0 (default value for int)
fmt.Printf("%#v\n", j.K("no").K("pher").Int())
// => jsn.Int{Value:0, IsValid:false}
Safely getting the "gogo" value in the first example above via vanilla Go
would look like 🙈:
var j map[string]interface{}
err := json.Unmarshal([]byte(`{<...>}`, &j)
if err != nil {
panic(err)
}
var value string
if g, ok := j["go"].(map[string]interface{}); ok {
if p, ok := g["pher"].([]interface{}); ok && len(p) > 2 {
if e, ok := p[2].(map[string]interface{}); ok {
if n, ok := e["name"].(string); ok {
value = n
}
}
}
}
fmt.Println(value)
// => gogo
j, _ := jsn.NewJson(`[1, "two", {"tree": true}]`)
for i, e := range j.Array().Elements() {
fmt.Printf("[%d]: %s\n", i, e.Stringify())
}
// =>
// [0]: 1
// [1]: "two"
// [2]: {"tree":true}
notArray := j.K("no").Array()
fmt.Println("valid?:", notArray.IsValid)
fmt.Println("len:", len(notArray.Elements()))
for i, e := range notArray.Elements() {
fmt.Printf("[%d]: %s\n", i, e.Stringify())
}
// =>
// valid?: false
// len: 0
j, _ := jsn.NewJson(`{
"name": "gopher",
"age": 10,
"data": {"go": true}
}`)
count := j.IterMap(func(k string, v jsn.Json) bool {
fmt.Printf(" %s: %s\n", k, v.Stringify())
return true // return false to break
})
fmt.Printf("iterated %d keys\n", count)
// =>
// name: "gopher"
// data: {"go":true}
// age: 10
// iterated 3 keys
jsn.Map
is just a fancy alias to map[string]interface{}
, but sometimes the little things in life make all the difference.
It also has some convinience methods for easirer marshling.
type Pixel struct {
X int
Y int
}
jm := jsn.Map{
"songs": []jsn.Map{
{"hip": "hop"},
{"hoo": "ray"},
},
"time": time.Now(),
"location": Pixel{X: 13, Y: 37},
}
fmt.Println(jm.Stringify())
// => {"location":{"X":13,"Y":37},"songs":[{"hip":"hop"},{"hoo":"ray"}],"time":"2017-09-08T14:40:23.903861328+03:00"}
fmt.Println(jm.Pretty())
// => same as above but pretty
Note: because interface{}
can be anything, it is possible to create a jsn.Map
that is not a valid JSON - i.e. json.Marshal()
will fail on it. This can happen if a value is not marshalable - see https://golang.org/pkg/encoding/json/#Marshal.
In such case String()
& Pretty()
will return an empty string