// Package gjson provides searching for json strings. package gjson import "strconv" // Type is Result type type Type int const ( // Null is a null json value Null Type = iota // False is a json false boolean False // Number is json number Number // String is a json string String // True is a json true boolean True // JSON is a raw block of JSON JSON ) // Result represents a json value that is returned from Get(). type Result struct { // Type is the json type Type Type // Raw is the raw json Raw string // Str is the json string Str string // Num is the json number Num float64 } // String returns a string representation of the value. func (t Result) String() string { switch t.Type { default: return "null" case False: return "false" case Number: return strconv.FormatFloat(t.Num, 'f', -1, 64) case String: return t.Str case JSON: return t.Raw case True: return "true" } } // Bool returns an boolean representation. func (t Result) Bool() bool { switch t.Type { default: return false case True: return true case String: return t.Str != "" && t.Str != "0" case Number: return t.Num != 0 } } // Int returns an integer representation. func (t Result) Int() int64 { switch t.Type { default: return 0 case True: return 1 case String: n, _ := strconv.ParseInt(t.Str, 10, 64) return n case Number: return int64(t.Num) } } // Float returns an float64 representation. func (t Result) Float() float64 { switch t.Type { default: return 0 case True: return 1 case String: n, _ := strconv.ParseFloat(t.Str, 64) return n case Number: return t.Num } } // Array returns back an array of children. The result must be a JSON array. func (t Result) Array() []Result { if t.Type != JSON { return nil } r := t.arrayOrMap('[', false) return r.a } // Map returns back an map of children. The result should be a JSON array. func (t Result) Map() map[string]Result { if t.Type != JSON { return map[string]Result{} } r := t.arrayOrMap('{', false) return r.o } // Get searches result for the specified path. // The result should be a JSON array or object. func (t Result) Get(path string) Result { return Get(t.Raw, path) } type arrayOrMapResult struct { a []Result ai []interface{} o map[string]Result oi map[string]interface{} vc byte } func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) { var json = t.Raw var i int var value Result var count int var key Result if vc == 0 { for ; i < len(json); i++ { if json[i] == '{' || json[i] == '[' { r.vc = json[i] i++ break } if json[i] > ' ' { goto end } } } else { for ; i < len(json); i++ { if json[i] == vc { i++ break } if json[i] > ' ' { goto end } } r.vc = vc } if r.vc == '{' { if valueize { r.oi = make(map[string]interface{}) } else { r.o = make(map[string]Result) } } else { if valueize { r.ai = make([]interface{}, 0) } else { r.a = make([]Result, 0) } } for ; i < len(json); i++ { if json[i] <= ' ' { continue } // get next value if json[i] == ']' || json[i] == '}' { break } switch json[i] { default: if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' { value.Type = Number value.Raw, value.Num = tonum(json[i:]) } else { continue } case '{', '[': value.Type = JSON value.Raw = squash(json[i:]) case 'n': value.Type = Null value.Raw = tolit(json[i:]) case 't': value.Type = True value.Raw = tolit(json[i:]) case 'f': value.Type = False value.Raw = tolit(json[i:]) case '"': value.Type = String value.Raw, value.Str = tostr(json[i:]) } i += len(value.Raw) - 1 if r.vc == '{' { if count%2 == 0 { key = value } else { if valueize { r.oi[key.Str] = value.Value() } else { r.o[key.Str] = value } } count++ } else { if valueize { r.ai = append(r.ai, value.Value()) } else { r.a = append(r.a, value) } } } end: return } // Parse parses the json and returns a result func Parse(json string) Result { var value Result for i := 0; i < len(json); i++ { if json[i] == '{' || json[i] == '[' { value.Type = JSON value.Raw = json[i:] // just take the entire raw break } if json[i] <= ' ' { continue } switch json[i] { default: if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' { value.Type = Number value.Raw, value.Num = tonum(json[i:]) } else { return Result{} } case 'n': value.Type = Null value.Raw = tolit(json[i:]) case 't': value.Type = True value.Raw = tolit(json[i:]) case 'f': value.Type = False value.Raw = tolit(json[i:]) case '"': value.Type = String value.Raw, value.Str = tostr(json[i:]) } break } return value } func squash(json string) string { // expects that the lead character is a '[' or '{' // squash the value, ignoring all nested arrays and objects. // the first '[' or '{' has already been read depth := 1 for i := 1; i < len(json); i++ { if json[i] >= '"' && json[i] <= '}' { switch json[i] { case '"': i++ s2 := i for ; i < len(json); i++ { if json[i] > '\\' { continue } if json[i] == '"' { // look for an escaped slash if json[i-1] == '\\' { n := 0 for j := i - 2; j > s2-1; j-- { if json[j] != '\\' { break } n++ } if n%2 == 0 { continue } } break } } case '{', '[': depth++ case '}', ']': depth-- if depth == 0 { return json[:i+1] } } } } return json } func tonum(json string) (raw string, num float64) { for i := 1; i < len(json); i++ { // less than dash might have valid characters if json[i] <= '-' { if json[i] <= ' ' || json[i] == ',' { // break on whitespace and comma raw = json[:i] num, _ = strconv.ParseFloat(raw, 64) return } // could be a '+' or '-'. let's assume so. continue } if json[i] < ']' { // probably a valid number continue } if json[i] == 'e' || json[i] == 'E' { // allow for exponential numbers continue } // likely a ']' or '}' raw = json[:i] num, _ = strconv.ParseFloat(raw, 64) return } raw = json num, _ = strconv.ParseFloat(raw, 64) return } func tolit(json string) (raw string) { for i := 1; i < len(json); i++ { if json[i] <= 'a' || json[i] >= 'z' { return json[:i] } } return json } func tostr(json string) (raw string, str string) { // expects that the lead character is a '"' for i := 1; i < len(json); i++ { if json[i] > '\\' { continue } if json[i] == '"' { return json[:i+1], json[1:i] } if json[i] == '\\' { i++ for ; i < len(json); i++ { if json[i] > '\\' { continue } if json[i] == '"' { // look for an escaped slash if json[i-1] == '\\' { n := 0 for j := i - 2; j > 0; j-- { if json[j] != '\\' { break } n++ } if n%2 == 0 { continue } } break } } return json[:i+1], unescape(json[1:i]) } } return json, json[1:] } // Exists returns true if value exists. // // if gjson.Get(json, "name.last").Exists(){ // println("value exists") // } func (t Result) Exists() bool { return t.Type != Null || len(t.Raw) != 0 } // Value returns one of these types: // // bool, for JSON booleans // float64, for JSON numbers // Number, for JSON numbers // string, for JSON string literals // nil, for JSON null // func (t Result) Value() interface{} { if t.Type == String { return t.Str } switch t.Type { default: return nil case False: return false case Number: return t.Num case JSON: r := t.arrayOrMap(0, true) if r.vc == '{' { return r.oi } else if r.vc == '[' { return r.ai } return nil case True: return true } } func parseString(json string, i int) (int, string, bool, bool) { var s = i for ; i < len(json); i++ { if json[i] > '\\' { continue } if json[i] == '"' { return i + 1, json[s-1 : i+1], false, true } if json[i] == '\\' { i++ for ; i < len(json); i++ { if json[i] > '\\' { continue } if json[i] == '"' { // look for an escaped slash if json[i-1] == '\\' { n := 0 for j := i - 2; j > 0; j-- { if json[j] != '\\' { break } n++ } if n%2 == 0 { continue } } return i + 1, json[s-1 : i+1], true, true } } break } } return i, json[s-1:], false, false } func parseNumber(json string, i int) (int, string) { var s = i i++ for ; i < len(json); i++ { if json[i] <= ' ' || json[i] == ',' || json[i] == ']' || json[i] == '}' { return i, json[s:i] } } return i, json[s:] } func parseLiteral(json string, i int) (int, string) { var s = i i++ for ; i < len(json); i++ { if json[i] < 'a' || json[i] > 'z' { return i, json[s:i] } } return i, json[s:] } type arrayPathResult struct { part string path string more bool alogok bool arrch bool alogkey string } func parseArrayPath(path string) (r arrayPathResult) { for i := 0; i < len(path); i++ { if path[i] == '.' { r.part = path[:i] r.path = path[i+1:] r.more = true return } if path[i] == '#' { r.arrch = true if i == 0 && len(path) > 1 && path[1] == '.' { r.alogok = true r.alogkey = path[2:] r.path = path[:1] } continue } } r.part = path r.path = "" return } type objectPathResult struct { part string path string wild bool uc bool more bool } func parseObjectPath(path string) (r objectPathResult) { for i := 0; i < len(path); i++ { if path[i]&0x60 == 0x60 { // alpha lowercase continue } if path[i] == '.' { r.part = path[:i] r.path = path[i+1:] r.more = true return } if path[i] == '*' || path[i] == '?' { r.wild = true continue } if path[i] > 0x7f { r.uc = true continue } if path[i] == '\\' { // go into escape mode. this is a slower path that // strips off the escape character from the part. epart := []byte(path[:i]) i++ if i < len(path) { epart = append(epart, path[i]) i++ for ; i < len(path); i++ { if path[i] > 0x7f { r.uc = true continue } if path[i] == '\\' { i++ if i < len(path) { epart = append(epart, path[i]) } continue } else if path[i] == '.' { r.part = string(epart) r.path = path[i+1:] r.more = true return } else if path[i] == '*' || path[i] == '?' { r.wild = true } epart = append(epart, path[i]) } } // append the last part r.part = string(epart) return } } r.part = path return } func parseSquash(json string, i int) (int, string) { // expects that the lead character is a '[' or '{' // squash the value, ignoring all nested arrays and objects. // the first '[' or '{' has already been read s := i i++ depth := 1 for ; i < len(json); i++ { if json[i] >= '"' && json[i] <= '}' { switch json[i] { case '"': i++ s2 := i for ; i < len(json); i++ { if json[i] > '\\' { continue } if json[i] == '"' { // look for an escaped slash if json[i-1] == '\\' { n := 0 for j := i - 2; j > s2-1; j-- { if json[j] != '\\' { break } n++ } if n%2 == 0 { continue } } break } } case '{', '[': depth++ case '}', ']': depth-- if depth == 0 { i++ return i, json[s:i] } } } } return i, json[s:] } func parseObject(c *parseContext, i int, path string) (int, bool) { var match, kesc, vesc, ok, hit bool var key, val string rp := parseObjectPath(path) for i < len(c.json) { for ; i < len(c.json); i++ { if c.json[i] == '"' { // parse_key_string // this is slightly different from getting s string value // because we don't need the outer quotes. i++ var s = i for ; i < len(c.json); i++ { if c.json[i] > '\\' { continue } if c.json[i] == '"' { i, key, kesc, ok = i+1, c.json[s:i], false, true goto parse_key_string_done } if c.json[i] == '\\' { i++ for ; i < len(c.json); i++ { if c.json[i] > '\\' { continue } if c.json[i] == '"' { // look for an escaped slash if c.json[i-1] == '\\' { n := 0 for j := i - 2; j > 0; j-- { if c.json[j] != '\\' { break } n++ } if n%2 == 0 { continue } } i, key, kesc, ok = i+1, c.json[s:i], true, true goto parse_key_string_done } } break } } i, key, kesc, ok = i, c.json[s:], false, false parse_key_string_done: break } if c.json[i] == '}' { return i + 1, false } } if !ok { return i, false } if rp.wild { if kesc { match = wildcardMatch(unescape(key), rp.part, rp.uc) } else { match = wildcardMatch(key, rp.part, rp.uc) } } else { if kesc { match = rp.part == unescape(key) } else { match = rp.part == key } } hit = match && !rp.more for ; i < len(c.json); i++ { switch c.json[i] { default: continue case '"': i++ i, val, vesc, ok = parseString(c.json, i) if !ok { return i, false } if hit { if vesc { c.value.Str = unescape(val[1 : len(val)-1]) } else { c.value.Str = val[1 : len(val)-1] } c.value.Raw = val c.value.Type = String return i, true } case '{': if match && !hit { i, hit = parseObject(c, i+1, rp.path) if hit { return i, true } } else { i, val = parseSquash(c.json, i) if hit { c.value.Raw = val c.value.Type = JSON return i, true } } case '[': if match && !hit { i, hit = parseArray(c, i+1, rp.path) if hit { return i, true } } else { i, val = parseSquash(c.json, i) if hit { c.value.Raw = val c.value.Type = JSON return i, true } } case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': i, val = parseNumber(c.json, i) if hit { c.value.Raw = val c.value.Type = Number c.value.Num, _ = strconv.ParseFloat(val, 64) return i, true } case 't', 'f', 'n': vc := c.json[i] i, val = parseLiteral(c.json, i) if hit { c.value.Raw = val switch vc { case 't': c.value.Type = True case 'f': c.value.Type = False } return i, true } } break } } return i, false } func parseArray(c *parseContext, i int, path string) (int, bool) { var match, vesc, ok, hit bool var val string var h int var alog []int var partidx int rp := parseArrayPath(path) if !rp.arrch { n, err := strconv.ParseUint(rp.part, 10, 64) if err != nil { partidx = -1 } else { partidx = int(n) } } for i < len(c.json) { if !rp.arrch { match = partidx == h hit = match && !rp.more } h++ if rp.alogok { alog = append(alog, i) } for ; i < len(c.json); i++ { switch c.json[i] { default: continue case '"': i++ i, val, vesc, ok = parseString(c.json, i) if !ok { return i, false } if hit { if rp.alogok { break } if vesc { c.value.Str = unescape(val[1 : len(val)-1]) } else { c.value.Str = val[1 : len(val)-1] } c.value.Raw = val c.value.Type = String return i, true } case '{': if match && !hit { i, hit = parseObject(c, i+1, rp.path) if hit { if rp.alogok { break } return i, true } } else { i, val = parseSquash(c.json, i) if hit { if rp.alogok { break } c.value.Raw = val c.value.Type = JSON return i, true } } case '[': if match && !hit { i, hit = parseArray(c, i+1, rp.path) if hit { if rp.alogok { break } return i, true } } else { i, val = parseSquash(c.json, i) if hit { if rp.alogok { break } c.value.Raw = val c.value.Type = JSON return i, true } } case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': i, val = parseNumber(c.json, i) if hit { if rp.alogok { break } c.value.Raw = val c.value.Type = Number c.value.Num, _ = strconv.ParseFloat(val, 64) return i, true } case 't', 'f', 'n': vc := c.json[i] i, val = parseLiteral(c.json, i) if hit { if rp.alogok { break } c.value.Raw = val switch vc { case 't': c.value.Type = True case 'f': c.value.Type = False } return i, true } case ']': if rp.arrch && rp.part == "#" { if rp.alogok { var jsons = make([]byte, 0, 64) jsons = append(jsons, '[') for j := 0; j < len(alog); j++ { res := Get(c.json[alog[j]:], rp.alogkey) if res.Exists() { if j > 0 { jsons = append(jsons, ',') } jsons = append(jsons, []byte(res.Raw)...) } } jsons = append(jsons, ']') c.value.Type = JSON c.value.Raw = string(jsons) return i + 1, true } else { if rp.alogok { break } c.value.Raw = val c.value.Type = Number c.value.Num = float64(h - 1) return i + 1, true } } return i + 1, false } break } } return i, false } // Get searches json for the specified path. // A path is in dot syntax, such as "name.last" or "age". // This function expects that the json is well-formed, and does not validate. // Invalid json will not panic, but it may return back unexpected results. // When the value is found it's returned immediately. // // A path is a series of keys seperated by a dot. // A key may contain special wildcard characters '*' and '?'. // To access an array value use the index as the key. // To get the number of elements in an array or to access a child path, use the '#' character. // The dot and wildcard character can be escaped with '\'. // // { // "name": {"first": "Tom", "last": "Anderson"}, // "age":37, // "children": ["Sara","Alex","Jack"], // "friends": [ // {"first": "James", "last": "Murphy"}, // {"first": "Roger", "last": "Craig"} // ] // } // "name.last" >> "Anderson" // "age" >> 37 // "children.#" >> 3 // "children.1" >> "Alex" // "child*.2" >> "Jack" // "c?ildren.0" >> "Sara" // "friends.#.first" >> [ "James", "Roger" ] // func Get(json, path string) Result { var i int var c = &parseContext{json: json} for ; i < len(c.json); i++ { if c.json[i] == '{' { i++ parseObject(c, i, path) break } if c.json[i] == '[' { i++ parseArray(c, i, path) break } } return c.value } type parseContext struct { json string value Result } // unescape unescapes a string func unescape(json string) string { //, error) { var str = make([]byte, 0, len(json)) for i := 0; i < len(json); i++ { switch { default: str = append(str, json[i]) case json[i] < ' ': return "" //, errors.New("invalid character in string") case json[i] == '\\': i++ if i >= len(json) { return "" //, errors.New("invalid escape sequence") } switch json[i] { default: return "" //, errors.New("invalid escape sequence") case '\\': str = append(str, '\\') case '/': str = append(str, '/') case 'b': str = append(str, '\b') case 'f': str = append(str, '\f') case 'n': str = append(str, '\n') case 'r': str = append(str, '\r') case 't': str = append(str, '\t') case '"': str = append(str, '"') case 'u': if i+5 > len(json) { return "" //, errors.New("invalid escape sequence") } i++ // extract the codepoint var code int for j := i; j < i+4; j++ { switch { default: return "" //, errors.New("invalid escape sequence") case json[j] >= '0' && json[j] <= '9': code += (int(json[j]) - '0') << uint(12-(j-i)*4) case json[j] >= 'a' && json[j] <= 'f': code += (int(json[j]) - 'a' + 10) << uint(12-(j-i)*4) case json[j] >= 'a' && json[j] <= 'f': code += (int(json[j]) - 'a' + 10) << uint(12-(j-i)*4) } } str = append(str, []byte(string(code))...) i += 3 // only 3 because we will increment on the for-loop } } } return string(str) //, nil } // Less return true if a token is less than another token. // The caseSensitive paramater is used when the tokens are Strings. // The order when comparing two different type is: // // Null < False < Number < String < True < JSON // func (t Result) Less(token Result, caseSensitive bool) bool { if t.Type < token.Type { return true } if t.Type > token.Type { return false } if t.Type == String { if caseSensitive { return t.Str < token.Str } return stringLessInsensitive(t.Str, token.Str) } if t.Type == Number { return t.Num < token.Num } return t.Raw < token.Raw } func stringLessInsensitive(a, b string) bool { for i := 0; i < len(a) && i < len(b); i++ { if a[i] >= 'A' && a[i] <= 'Z' { if b[i] >= 'A' && b[i] <= 'Z' { // both are uppercase, do nothing if a[i] < b[i] { return true } else if a[i] > b[i] { return false } } else { // a is uppercase, convert a to lowercase if a[i]+32 < b[i] { return true } else if a[i]+32 > b[i] { return false } } } else if b[i] >= 'A' && b[i] <= 'Z' { // b is uppercase, convert b to lowercase if a[i] < b[i]+32 { return true } else if a[i] > b[i]+32 { return false } } else { // neither are uppercase if a[i] < b[i] { return true } else if a[i] > b[i] { return false } } } return len(a) < len(b) } // wilcardMatch returns true if str matches pattern. This is a very // simple wildcard match where '*' matches on any number characters // and '?' matches on any one character. func wildcardMatch(str, pattern string, uc bool) bool { if pattern == "*" { return true } if !uc { return deepMatch(str, pattern) } rstr := make([]rune, 0, len(str)) rpattern := make([]rune, 0, len(pattern)) for _, r := range str { rstr = append(rstr, r) } for _, r := range pattern { rpattern = append(rpattern, r) } return deepMatchRune(rstr, rpattern) } func deepMatch(str, pattern string) bool { for len(pattern) > 0 { switch pattern[0] { default: if len(str) == 0 || str[0] != pattern[0] { return false } case '?': if len(str) == 0 { return false } case '*': return deepMatch(str, pattern[1:]) || (len(str) > 0 && deepMatch(str[1:], pattern)) } str = str[1:] pattern = pattern[1:] } return len(str) == 0 && len(pattern) == 0 } func deepMatchRune(str, pattern []rune) bool { for len(pattern) > 0 { switch pattern[0] { default: if len(str) == 0 || str[0] != pattern[0] { return false } case '?': if len(str) == 0 { return false } case '*': return deepMatchRune(str, pattern[1:]) || (len(str) > 0 && deepMatchRune(str[1:], pattern)) } str = str[1:] pattern = pattern[1:] } return len(str) == 0 && len(pattern) == 0 }