Xpath与JsonPath符号对比
JSONPath表达式
JSONPath表达式总是以与XPath相同的方式引用JSON结构
表达式与XML文档结合使用。 由于JSON结构是
通常是匿名的,并不一定有* root成员对象* JSONPath
假设分配给外层对象的抽象名称$
。
JSONPath表达式可以使用点符号:
@H_
502_18@$
.store.book[
0]
.title
或方括号:
@H_
502_18@$[
'store'][
'book'][
0][
'title']
为输入路径。内部或输出路径将始终转换为更一般的方括号。
JSONPath允许成员名称和数组索引使用通配符*
. 它从E4X和数组切片语法借用后代运算符..
来自ECMASCRIPT 4的提议[start:end:step]
.
底层脚本语言(<expr>)
的表达式可以用作一个
显式名称或索引的替代,如下所示:
@H_
502_18@$
.store.book[(@
.length-
1)]
.title
Filter expressions are supported via
the Syntax ?(<boolean expr>)
,as in:
使用符号@
作为当前对象。过滤器表达式通过
语法?(<boolean expr>)
,如下所示:
@H_
502_18@$
.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结构:
@H_
502_18@{ "
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 },"author": "Herman Melville","title": "Moby Dick","isbn": "0-553-21311-3","price": 8.99 },"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)]<br>$..book[-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 |
JsonPath.cs
@H_
502_18@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<T> SelectNodes<T>(
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,
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,
object value,
string path);
public Interpreter(IJsonPathValueSystem valueSystem,Func<
string,
object> eval)
{
_eval = eval ??
delegate
{
return null;
};
_system = valueSystem ?? DefaultValueSystem;
}
sealed class TraceArgs
{
public readonly string Expr;
public readonly object Value;
public readonly string Path;
public TraceArgs(
string expr,
string path)
{
Expr = expr;
Value =
value;
Path = path;
}
}
public IEnumerable<T> Trace<T>(
string expr,
string path,
string[],T> resultor)
{
return Trace(Args(expr,
value,path),resultor);
}
static TraceArgs Args(
string expr,
string path)
{
return new TraceArgs(expr,path);
}
IEnumerable<T> Trace<T>(TraceArgs args,T> resultor)
{
var stack =
new Stack<TraceArgs>();
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,path,(m,l,x,v,p) => stack.Push(Args(m +
";" + x,p)));
}
else if (atom ==
"..")
{
Walk(atom,p) =>
{
var result = Index(v,m.ToString());
if (result !=
null && !_system.IsPrimitive(result))
stack.Push(Args(
"..;" + x,result,p +
";" + m));
});
stack.Push(Args(tail,path));
}
else if (atom.Length >
2 && atom[
0] ==
'(' && atom[atom.Length -
1] ==
')')
{
stack.Push(Args(_eval(atom,path.Substring(path.LastIndexOf(
';') +
1)) +
";" + tail,path));
}
else if (atom.Length >
3 && atom[
0] ==
'?' && atom[
1] ==
'(' && atom[atom.Length -
1] ==
')')
{
Walk(atom,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,p));
});
}
else if (RegExp.IsMatch(atom,
@"^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$"))
{
foreach (
var a
in Slice(atom,path).Reverse())
stack.Push(a);
}
else if (atom.IndexOf(
',') >=
0)
{
foreach (
var part
in RegExp.Split(atom,
@"'?,'?").Reverse())
stack.Push(Args(part +
";" + tail,path));
}
}
}
void Walk(
string loc,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,path);
}
else if (_system.IsObject(
value))
{
foreach (
var key
in _system.GetMembers(
value).Reverse())
callback(key,path);
}
}
static IEnumerable<TraceArgs> Slice(
string loc,
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,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,replacement,MatchEvaluator evaluator)
{
return Regex.Replace(input,evaluator,Options);
}
public static IEnumerable<
string>
Split(
string input,
string pattern)
{
return Regex.Split(input,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;
}
}
}
}