Xpath与JsonPath符号对比
JSONPath表达式总是以与XPath相同的方式引用JSON结构
表达式与XML文档结合使用。 由于JSON结构是
通常是匿名的,并不一定有* root成员对象* JSONPath
假设分配给外层对象的抽象名称$
。
JSONPath表达式可以使用点符号:
$.store.book[0].title
或方括号:
$['store']['book'][0]['title']
为输入路径。内部或输出路径将始终转换为更一般的方括号。
JSONPath允许成员名称和数组索引使用通配符*
. 它从E4X和数组切片语法借用后代运算符..
来自ECMASCRIPT 4的提议[start:end:step]
.
底层脚本语言(
的表达式可以用作一个
显式名称或索引的替代,如下所示:
$.store.book[(@.length-1)].title
Filter expressions are supported via
the syntax ?(
, as in:
使用符号@
作为当前对象。过滤器表达式通过
语法?(
,如下所示:
$.store.book[?(@.price < 10)].title
以下是对JSONPath的完整概述和并排比较语法元素与XPath相对应:
XPath | JSONPath | Description |
---|---|---|
/ |
$ |
The root object/element |
. |
@ |
The current object/element |
/ |
. or [] |
Child operator |
.. |
n/a | Parent operator |
// |
.. |
Recursive descent. JSONPath borrows this syntax from E4X. |
* |
* |
Wildcard. All objects/elements regardless their names. |
@ |
n/a | Attribute access. JSON structures don’t have attributes. |
[] |
[] |
Subscript operator. XPath uses it to iterate over element collections and for predicates. In Javascript and JSON it is the native array operator. |
` | ` | [,] |
n/a | [start:end:step] |
Array slice operator borrowed from ES4. |
[] |
?() |
Applies a filter (script) expression. |
n/a | () |
Script expression, using the underlying script engine. |
() |
n/a | Grouping in XPath |
让我们通过更多的例子来练习JSONPath表达式。 我们从一开始
在代表书店的XML示例之后构建的简单JSON结构:
{ "store": {
"book": [
{ "category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{ "category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{ "category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{ "category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
XPath | JSONPath | Result | Notes |
---|---|---|---|
/store/book/author |
$.store.book[*].author |
The authors of all books in the store | |
//author |
$..author |
All authors | |
/store/* |
$.store.* |
All things in store, which are some books and a red bicycle | |
/store//price |
$.store..price |
The price of everything in the store | |
//book[3] |
$..book[2] |
The third book | |
//book[last()] |
$..book[(@.length-1)] |
The last book in order | |
//book[position()<3] |
$..book[0,1] $..book[:2] |
The first two books | |
`//book/*[self::category | self::author]or //book/(category,author)` in XPath 2.0 |
$..book[category,author] |
The categories and authors of all books |
//book[isbn] |
$..book[?(@.isbn)] |
Filter all books with isbn number |
|
//book[price<10] |
$..book[?(@.price<10)] |
Filter all books cheapier than 10 | |
//*[price>19]/.. |
$..[?(@.price>19)] |
Categories with things more expensive than 19 | Parent (caret) not present in original spec |
//* |
$..* |
All elements in XML document; all members of JSON structure | |
/store/book/[position()!=1] |
$.store.book[?(@path !== "$[\'store\'][\'book\'][0]")] |
All books besides that at the path pointing to the first | @path not present in original spec |
namespace JsonPath
{
#region Imports
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
#endregion
public interface IJsonPathValueSystem
{
bool HasMember(object value, string member);
object GetMemberValue(object value, string member);
IEnumerable<string> GetMembers(object value);
bool IsObject(object value);
bool IsArray(object value);
bool IsPrimitive(object value);
}
public sealed class JsonPathContext
{
public static readonly JsonPathContext Default = new JsonPathContext();
public Func<string /* script */,
object /* value */,
string /* context */,
object /* result */>
ScriptEvaluator { get; set; }
public IJsonPathValueSystem ValueSystem { get; set; }
public IEnumerable<object> Select(object obj, string expr)
{
return SelectNodes(obj, expr, (v, _) => v);
}
public IEnumerable SelectNodes(object obj, string expr, Func<object, string, T> resultor)
{
if (obj == null) throw new ArgumentNullException("obj");
if (resultor == null) throw new ArgumentNullException("resultor");
var i = new Interpreter(ValueSystem, ScriptEvaluator);
expr = Normalize(expr);
if (expr.Length >= 1 && expr[0] == '$') // ^\$:?
expr = expr.Substring(expr.Length >= 2 && expr[1] == ';' ? 2 : 1);
return i.Trace(expr, obj, "$", (value, path) => resultor(value, AsBracketNotation(path)));
}
static string Normalize(string expr)
{
var subx = new List<string>();
expr = RegExp.Replace(expr, @"[\['](\??\(.*?\))[\]']", m =>
{
subx.Add(m.Groups[1].Value);
return "[#" + subx.Count.ToString(CultureInfo.InvariantCulture) + "]";
});
expr = RegExp.Replace(expr, @"'?\.'?|\['?", ";");
expr = RegExp.Replace(expr, @";;;|;;", ";..;");
expr = RegExp.Replace(expr, @";$|'?\]|'$", string.Empty);
expr = RegExp.Replace(expr, @"#([0-9]+)", m =>
{
var index = int.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
return subx[index];
});
return expr;
}
public static string AsBracketNotation(string[] indicies)
{
if (indicies == null)
throw new ArgumentNullException("indicies");
var sb = new StringBuilder();
foreach (var index in indicies)
{
if (sb.Length == 0)
{
sb.Append('$');
}
else
{
sb.Append('[');
if (RegExp.IsMatch(index, @"^[0-9*]+$"))
sb.Append(index);
else
sb.Append('\'').Append(index).Append('\'');
sb.Append(']');
}
}
return sb.ToString();
}
static int ParseInt(string str, int defaultValue = 0)
{
if (string.IsNullOrEmpty(str))
return defaultValue;
try
{
return int.Parse(str, NumberStyles.None, CultureInfo.InvariantCulture);
}
catch (FormatException)
{
return defaultValue;
}
}
sealed class Interpreter
{
readonly Func<string, object, string, object> _eval;
readonly IJsonPathValueSystem _system;
static readonly IJsonPathValueSystem DefaultValueSystem = new BasicValueSystem();
static readonly char[] Colon = { ':' };
static readonly char[] Semicolon = { ';' };
delegate void WalkCallback(object member, string loc, string expr, object value, string path);
public Interpreter(IJsonPathValueSystem valueSystem, Func<string, object, string, object> eval)
{
_eval = eval ?? delegate
{
// expr中的@符号必须专门解释才能解析为值。 在JavaScript中,实现将如下所示:
// return obj && value && eval(expr.replace(/@/g, "value"));
return null;
};
_system = valueSystem ?? DefaultValueSystem;
}
sealed class TraceArgs
{
public readonly string Expr;
public readonly object Value;
public readonly string Path;
public TraceArgs(string expr, object value, string path)
{
Expr = expr;
Value = value;
Path = path;
}
}
public IEnumerable Trace(string expr, object value, string path, Func<object, string[], T> resultor)
{
return Trace(Args(expr, value, path), resultor);
}
static TraceArgs Args(string expr, object value, string path)
{
return new TraceArgs(expr, value, path);
}
IEnumerable Trace(TraceArgs args, Func<object, string[], T> resultor)
{
var stack = new Stack();
stack.Push(args);
while (stack.Count > 0)
{
var popped = stack.Pop();
var expr = popped.Expr;
var value = popped.Value;
var path = popped.Path;
if (string.IsNullOrEmpty(expr))
{
if (path != null)
yield return resultor(value, path.Split(Semicolon));
continue;
}
var i = expr.IndexOf(';');
var atom = i >= 0 ? expr.Substring(0, i) : expr;
var tail = i >= 0 ? expr.Substring(i + 1) : string.Empty;
if (value != null && _system.HasMember(value, atom))
{
stack.Push(Args(tail, Index(value, atom), path + ";" + atom));
}
else if (atom == "*")
{
Walk(atom, tail, value, path, (m, l, x, v, p) => stack.Push(Args(m + ";" + x, v, p)));
}
else if (atom == "..")
{
Walk(atom, tail, value, path, (m, l, x, v, p) =>
{
var result = Index(v, m.ToString());
if (result != null && !_system.IsPrimitive(result))
stack.Push(Args("..;" + x, result, p + ";" + m));
});
stack.Push(Args(tail, value, path));
}
else if (atom.Length > 2 && atom[0] == '(' && atom[atom.Length - 1] == ')') // [(exp)]
{
stack.Push(Args(_eval(atom, value, path.Substring(path.LastIndexOf(';') + 1)) + ";" + tail, value, path));
}
else if (atom.Length > 3 && atom[0] == '?' && atom[1] == '(' && atom[atom.Length - 1] == ')') // [?(exp)]
{
Walk(atom, tail, value, path, (m, l, x, v, p) =>
{
var result = _eval(RegExp.Replace(l, @"^\?\((.*?)\)$", "$1"),
Index(v, m.ToString()), m.ToString());
if (Convert.ToBoolean(result, CultureInfo.InvariantCulture))
stack.Push(Args(m + ";" + x, v, p));
});
}
else if (RegExp.IsMatch(atom, @"^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$")) // [start:end:step] Phyton slice syntax
{
foreach (var a in Slice(atom, tail, value, path).Reverse())
stack.Push(a);
}
else if (atom.IndexOf(',') >= 0) // [name1,name2,...]
{
foreach (var part in RegExp.Split(atom, @"'?,'?").Reverse())
stack.Push(Args(part + ";" + tail, value, path));
}
}
}
void Walk(string loc, string expr, object value, string path, WalkCallback callback)
{
if (_system.IsPrimitive(value))
return;
if (_system.IsArray(value))
{
var list = (IList) value;
for (var i = list.Count - 1; i >= 0; i--)
callback(i, loc, expr, value, path);
}
else if (_system.IsObject(value))
{
foreach (var key in _system.GetMembers(value).Reverse())
callback(key, loc, expr, value, path);
}
}
static IEnumerable Slice(string loc, string expr, object value, string path)
{
var list = value as IList;
if (list == null)
yield break;
var length = list.Count;
var parts = loc.Split(Colon);
var start = ParseInt(parts[0]);
var end = ParseInt(parts[1], list.Count);
var step = parts.Length > 2 ? ParseInt(parts[2], 1) : 1;
start = (start < 0) ? Math.Max(0, start + length) : Math.Min(length, start);
end = (end < 0) ? Math.Max(0, end + length) : Math.Min(length, end);
for (var i = start; i < end; i += step)
yield return Args(i + ";" + expr, value, path);
}
object Index(object obj, string member)
{
return _system.GetMemberValue(obj, member);
}
}
static class RegExp
{
const RegexOptions Options = RegexOptions.ECMAScript;
public static bool IsMatch(string input, string pattern)
{
return Regex.IsMatch(input, pattern, Options);
}
public static string Replace(string input, string pattern, string replacement)
{
return Regex.Replace(input, pattern, replacement, Options);
}
public static string Replace(string input, string pattern, MatchEvaluator evaluator)
{
return Regex.Replace(input, pattern, evaluator, Options);
}
public static IEnumerable<string> Split(string input, string pattern)
{
return Regex.Split(input, pattern, Options);
}
}
sealed class BasicValueSystem : IJsonPathValueSystem
{
public bool HasMember(object value, string member)
{
if (IsPrimitive(value))
return false;
var dict = value as IDictionary;
if (dict != null)
return dict.Contains(member);
var list = value as IList;
if (list != null)
{
var index = ParseInt(member, -1);
return index >= 0 && index < list.Count;
}
return false;
}
public object GetMemberValue(object value, string member)
{
if (IsPrimitive(value))
throw new ArgumentException("value");
var dict = value as IDictionary;
if (dict != null)
return dict[member];
var list = (IList) value;
var index = ParseInt(member, -1);
if (index >= 0 && index < list.Count)
return list[index];
return null;
}
public IEnumerable<string> GetMembers(object value)
{
return ((IDictionary) value).Keys.Cast<string>();
}
public bool IsObject(object value)
{
return value is IDictionary;
}
public bool IsArray(object value)
{
return value is IList;
}
public bool IsPrimitive(object value)
{
if (value == null)
throw new ArgumentNullException("value");
return Type.GetTypeCode(value.GetType()) != TypeCode.Object;
}
}
}
}