Browse Source

JSON Query

ls 4 years ago
parent
commit
3188d6a491
1 changed files with 340 additions and 0 deletions
  1. 340 0
      json.go

+ 340 - 0
json.go

@@ -0,0 +1,340 @@
+/*
+Copyright (c) 2012, Jason Moiron
+
+ 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.
+ // */
+// https://github.com/jmoiron/jsonq
+
+package util
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"strconv"
+)
+
+// JSONQuery is an object that enables querying of a Go map with a simple
+// positional query language.
+type JSONQuery struct {
+	data map[string]interface{}
+}
+
+// NewJSONQuery creates a new JSONQuery obj from []byte.
+func NewJSONQuery(data []byte) (jq *JSONQuery, err error) {
+	obj := make(map[string]interface{})
+	// bytes.NewReader(data) strings.NewReader(jsonstring)
+	dec := json.NewDecoder(bytes.NewReader(data))
+	err = dec.Decode(&obj)
+	if err != nil {
+		return
+	}
+
+	jq = new(JSONQuery)
+	jq.data = obj
+	return
+}
+
+// Bool extracts a bool the JSONQuery
+func (jq *JSONQuery) Bool(s ...string) (bool, error) {
+	val, err := rquery(jq.data, s...)
+	if err != nil {
+		return false, err
+	}
+	return boolFromInterface(val)
+}
+
+// Float extracts a float from the JsonQuery
+func (jq *JSONQuery) Float(s ...string) (float64, error) {
+	val, err := rquery(jq.data, s...)
+	if err != nil {
+		return 0.0, err
+	}
+	return floatFromInterface(val)
+}
+
+// Int extracts an int from the JsonQuery
+func (jq *JSONQuery) Int(s ...string) (int, error) {
+	val, err := rquery(jq.data, s...)
+	if err != nil {
+		return 0, err
+	}
+	return intFromInterface(val)
+}
+
+// String extracts a string from the JsonQuery
+func (jq *JSONQuery) String(s ...string) (string, error) {
+	val, err := rquery(jq.data, s...)
+	if err != nil {
+		return "", err
+	}
+	return stringFromInterface(val)
+}
+
+// Object extracts a json object from the JsonQuery
+func (jq *JSONQuery) Object(s ...string) (map[string]interface{}, error) {
+	val, err := rquery(jq.data, s...)
+	if err != nil {
+		return map[string]interface{}{}, err
+	}
+	return objectFromInterface(val)
+}
+
+// Array extracts a []interface{} from the JsonQuery
+func (jq *JSONQuery) Array(s ...string) ([]interface{}, error) {
+	val, err := rquery(jq.data, s...)
+	if err != nil {
+		return []interface{}{}, err
+	}
+	return arrayFromInterface(val)
+}
+
+// Interface extracts an interface{} from the JsonQuery
+func (jq *JSONQuery) Interface(s ...string) (interface{}, error) {
+	val, err := rquery(jq.data, s...)
+	if err != nil {
+		return nil, err
+	}
+	return val, nil
+}
+
+// ArrayOfStrings extracts an array of strings from some json
+func (jq *JSONQuery) ArrayOfStrings(s ...string) ([]string, error) {
+	array, err := jq.Array(s...)
+	if err != nil {
+		return []string{}, err
+	}
+	toReturn := make([]string, len(array))
+	for index, val := range array {
+		toReturn[index], err = stringFromInterface(val)
+		if err != nil {
+			return toReturn, err
+		}
+	}
+	return toReturn, nil
+}
+
+// ArrayOfInts extracts an array of ints from some json
+func (jq *JSONQuery) ArrayOfInts(s ...string) ([]int, error) {
+	array, err := jq.Array(s...)
+	if err != nil {
+		return []int{}, err
+	}
+	toReturn := make([]int, len(array))
+	for index, val := range array {
+		toReturn[index], err = intFromInterface(val)
+		if err != nil {
+			return toReturn, err
+		}
+	}
+	return toReturn, nil
+}
+
+// ArrayOfFloats extracts an array of float64s from some json
+func (jq *JSONQuery) ArrayOfFloats(s ...string) ([]float64, error) {
+	array, err := jq.Array(s...)
+	if err != nil {
+		return []float64{}, err
+	}
+	toReturn := make([]float64, len(array))
+	for index, val := range array {
+		toReturn[index], err = floatFromInterface(val)
+		if err != nil {
+			return toReturn, err
+		}
+	}
+	return toReturn, nil
+}
+
+// ArrayOfBools extracts an array of bools from some json
+func (jq *JSONQuery) ArrayOfBools(s ...string) ([]bool, error) {
+	array, err := jq.Array(s...)
+	if err != nil {
+		return []bool{}, err
+	}
+	toReturn := make([]bool, len(array))
+	for index, val := range array {
+		toReturn[index], err = boolFromInterface(val)
+		if err != nil {
+			return toReturn, err
+		}
+	}
+	return toReturn, nil
+}
+
+// ArrayOfObjects extracts an array of map[string]interface{} (objects) from some json
+func (jq *JSONQuery) ArrayOfObjects(s ...string) ([]map[string]interface{}, error) {
+	array, err := jq.Array(s...)
+	if err != nil {
+		return []map[string]interface{}{}, err
+	}
+	toReturn := make([]map[string]interface{}, len(array))
+	for index, val := range array {
+		toReturn[index], err = objectFromInterface(val)
+		if err != nil {
+			return toReturn, err
+		}
+	}
+	return toReturn, nil
+}
+
+// ArrayOfArrays extracts an array of []interface{} (arrays) from some json
+func (jq *JSONQuery) ArrayOfArrays(s ...string) ([][]interface{}, error) {
+	array, err := jq.Array(s...)
+	if err != nil {
+		return [][]interface{}{}, err
+	}
+	toReturn := make([][]interface{}, len(array))
+	for index, val := range array {
+		toReturn[index], err = arrayFromInterface(val)
+		if err != nil {
+			return toReturn, err
+		}
+	}
+	return toReturn, nil
+}
+
+// Matrix2D is an alias for ArrayOfArrays
+func (jq *JSONQuery) Matrix2D(s ...string) ([][]interface{}, error) {
+	return jq.ArrayOfArrays(s...)
+}
+
+// Recursively query a decoded json blob
+func rquery(blob interface{}, s ...string) (interface{}, error) {
+	var (
+		val interface{}
+		err error
+	)
+	val = blob
+	for _, q := range s {
+		val, err = query(val, q)
+		if err != nil {
+			return nil, err
+		}
+	}
+	switch val.(type) {
+	case nil:
+		return nil, fmt.Errorf("nil value")
+	}
+	return val, nil
+}
+
+// query a json blob for a single field or index.  If query is a string, then
+// the blob is treated as a json object (map[string]interface{}).  If query is
+// an integer, the blob is treated as a json array ([]interface{}).  Any kind
+// of key or index error will result in a nil return value with an error set.
+func query(blob interface{}, query string) (interface{}, error) {
+	index, err := strconv.Atoi(query)
+	// if it's an integer, then we treat the current interface as an array
+	if err == nil {
+		switch blob.(type) {
+		case []interface{}:
+		default:
+			return nil, fmt.Errorf("Array index on non-array")
+		}
+		if len(blob.([]interface{})) > index {
+			return blob.([]interface{})[index], nil
+		}
+		return nil, fmt.Errorf("Array index %d out of bounds", index)
+	}
+
+	// blob is likely an object, but verify first
+	switch blob.(type) {
+	case map[string]interface{}:
+	default:
+		return nil, fmt.Errorf("Object lookup \"%s\" on non-object", query)
+	}
+
+	val, ok := blob.(map[string]interface{})[query]
+	if !ok {
+		return nil, fmt.Errorf("Object does not contain field %s", query)
+	}
+	return val, nil
+}
+
+// stringFromInterface converts an interface{} to a string and returns an error if types don't match.
+func stringFromInterface(val interface{}) (string, error) {
+	switch val.(type) {
+	case string:
+		return val.(string), nil
+	}
+	return "", fmt.Errorf("Expected string value for String, got \"%v\"", val)
+}
+
+// boolFromInterface converts an interface{} to a bool and returns an error if types don't match.
+func boolFromInterface(val interface{}) (bool, error) {
+	switch val.(type) {
+	case bool:
+		return val.(bool), nil
+	}
+	return false, fmt.Errorf("Expected boolean value for Bool, got \"%v\"", val)
+}
+
+// floatFromInterface converts an interface{} to a float64 and returns an error if types don't match.
+func floatFromInterface(val interface{}) (float64, error) {
+	switch val.(type) {
+	case float64:
+		return val.(float64), nil
+	case int:
+		return float64(val.(int)), nil
+	case string:
+		fval, err := strconv.ParseFloat(val.(string), 64)
+		if err == nil {
+			return fval, nil
+		}
+	}
+	return 0.0, fmt.Errorf("Expected numeric value for Float, got \"%v\"", val)
+}
+
+// intFromInterface converts an interface{} to an int and returns an error if types don't match.
+func intFromInterface(val interface{}) (int, error) {
+	switch val.(type) {
+	case float64:
+		return int(val.(float64)), nil
+	case string:
+		ival, err := strconv.ParseFloat(val.(string), 64)
+		if err == nil {
+			return int(ival), nil
+		}
+	case int:
+		return val.(int), nil
+	}
+	return 0, fmt.Errorf("Expected numeric value for Int, got \"%v\"", val)
+}
+
+// objectFromInterface converts an interface{} to a map[string]interface{} and returns an error if types don't match.
+func objectFromInterface(val interface{}) (map[string]interface{}, error) {
+	switch val.(type) {
+	case map[string]interface{}:
+		return val.(map[string]interface{}), nil
+	}
+	return map[string]interface{}{}, fmt.Errorf("Expected json object for Object, got \"%v\"", val)
+}
+
+// arrayFromInterface converts an interface{} to an []interface{} and returns an error if types don't match.
+func arrayFromInterface(val interface{}) ([]interface{}, error) {
+	switch val.(type) {
+	case []interface{}:
+		return val.([]interface{}), nil
+	}
+	return []interface{}{}, fmt.Errorf("Expected json array for Array, got \"%v\"", val)
+}