123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- /*
- 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 myth
- 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)
- }
|