JsonPath与Xpath对比学习

Xpath与JsonPath符号对比

这里写图片描述


JSONPath表达式

JSONPath表达式总是以与XPath相同的方式引用JSON结构

表达式与XML文档结合使用。 由于JSON结构是

通常是匿名的,并不一定有* root成员对象* JSONPath

假设分配给外层对象的抽象名称$

JSONPath表达式可以使用点符号:

    $.store.book[0].title

或方括号:

   $['store']['book'][0]['title']

为输入路径。内部或输出路径将始终转换为更一般的方括号。

JSONPath允许成员名称和数组索引使用通配符*. 它从E4X数组切片语法借用后代运算符..
来自ECMASCRIPT 4的提议[start:end:step].

底层脚本语言(<expr>)的表达式可以用作一个
显式名称或索引的替代,如下所示:

   $.store.book[(@.length-1)].title

Filter expressions are supported via
the syntax ?(<boolean expr>), as in:
使用符号@ 作为当前对象。过滤器表达式通过
语法?(<boolean expr>),如下所示:

    $.store.book[?(@.price < 10)].title

以下是对JSONPath的完整概述和并排比较语法元素与XPath相对应:

XPathJSONPathDescription
/$The root object/element
.@The current object/element
/. or []Child operator
..n/aParent operator
//..Recursive descent. JSONPath borrows this syntax from E4X.
**Wildcard. All objects/elements regardless their names.
@n/aAttribute 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/aGrouping 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
        }
      }
    }
XPathJSONPathResultNotes
/store/book/author$.store.book[*].authorThe authors of all books in the store
//author$..authorAll authors
/store/*$.store.*All things in store, which are some books and a red bicycle
/store//price$.store..priceThe 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::categoryself::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 19Parent (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


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, 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<T> Trace<T>(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<T> Trace<T>(TraceArgs args, Func<object, string[], 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, 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<TraceArgs> 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;
            }
        }
    }
}
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值