九、使用 JSONPath 和 LINQ 查询 JSON

有时,您可能想要做的只是从一些 JSON 格式的数据中提取一两个字段,而不是将一个 JSON blob 解析为一个类并处理它的所有字段。 使用 JSONPath 或 LINQ(使用 Json.NET),您就可以做到这一点。 在这里,你会发现以下食谱:

  • 使用 JSONPath 点符号查询 JSON 文档
  • 使用 JSONPath 括号符号查询 JSON 文档
  • 使用 JSONPath 脚本构造更复杂的查询
  • 在 web 应用中使用 JSONPath
  • 在 Node.js 应用中使用 JSONPath
  • 在 PHP 应用中使用 JSONPath
  • 在 Python 应用中使用 JSONPath
  • 在 Java 应用中使用 JSONPath
  • 使用 JSONPath 和 SelectToken 来查询 c#应用中的 JSONPath 表达式
  • 使用带 Json 的 LINQ。 NET 来在您的 c#应用中查询 JSON

简介

XML 最大的优势之一是 XPath,这是一种面向查询的语言,用于查询 XML 文档的子部分。 Stefan Goessner 提出了 JSONPath 查询语言,这种语言具有类似于 XPath 的特性,可以只提取应用需要的 JSON 文档的部分。

请注意,仍然有一些东西在进行解析:您不会不图回报,而 JSONPath 实现要求 JSON 解析至少具有类似的内存和运行时特征。 但是,如果您正在开发的平台有一个 JSONPath 库,那么 JSONPath 可以生成更可读的代码,因为您不需要模拟整个类,只为了提取一两个字段或跨 JSON 值集合总结一个字段。

如果您习惯于为 Microsoft 平台开发,您肯定知道 Microsoft 的Language Independent Query(LINQ)语言,它允许您对可枚举数据结构使用写声明性查询。 虽然 JSON 解析的。net 实现只提供了基本的 LINQ 支持,但顽强的 JSON。 NET 库的实现支持 LINQ 和 JSONPath,允许您使用流利的或语句语法对 JSON 文档进行声明性查询。

要使用 JSONPath 或 LINQ 中的,您需要一个支持它的库。 当我写这篇文章时,有一些库支持 JavaScript 的 JSONPath,这是 Node.js、PHP、c#、Python 和 Java 的 JavaScript 风格。 当然,如果您想使用 LINQ,您需要在。net 平台上使用 c#、f#或 Visual Basic 等语言运行您的应用。 因此,大多数菜谱都有两个步骤:如何下载支持 JSONPath 的库,以及在应用中调用 JSONPath 代码的实际步骤。

大多数 JSONPath 示例使用 Goessner 的示例文档,其中包含来自一个假想书店的记录,在本章中,我们也将继续使用该示例。 我们的 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
    }
  }
}

如你所见,我们有一个存储对象,它有一组书和一辆自行车。 每本书都有一个类别、作者、书名和价格。 将 JSON 文档像这样表示为一个类是很困难的,因为图书记录和自行车记录的结构非常不同; 您可以使用 type-unsafe 查询方法,我们讨论了在第一章,阅读和写作 JSON 在客户机上第二章,阅读和写作 JSON 在服务器,来解析这样的文档和遍历文档,虽然一个更好的选择对于大多数应用是 JSONPath, 你们很快就会看到。 让我们从如何查询文档中的各个字段开始。

使用 JSONPath 点符号查询 JSON 文档

JSONPath 使用表达式以点表示法或括号表示法写入,表示 JSON 文档中字段的遍历。 点分隔字段名,就像它们是对象属性一样。

How to do it…

下面是一些点符号的例子:

$.store.book[0].title
$.store.book[*].title
$.store..price
$..book[3]

How it works…

在第一行中,我们引用了商店中的第一本(从零开始计算)书,返回标题字段。 第二行与之相似,只是它返回所有书籍的所有标题的集合。 第三个示例返回商店集合中所有记录中的所有价格字段的集合。 第四个示例查找商店中的第四个图书项目。

除了使用..*外,这种符号相当直观。 这些是 JSONPath 使用的一些特殊字符的示例,它们用于表示整个文档中的片段。

There's more…

JSONPath 定义了以下特殊字符,您可以在编写查询时使用:

  • $符号指的是根对象或元素。
  • 符号@指当前的对象或元素。
  • .操作符是点-子操作符,用来表示当前元素的子元素。
  • []操作符是下标操作符,用于表示当前元素的子元素(通过名称或下标)。
  • *操作符是一个通配符,返回所有对象或元素,而不管它们的名称。
  • ,操作符是联合操作符,返回所指示的子索引或索引的并集。
  • :操作符是数组切片操作符,因此可以使用[start:end:step]语法对集合进行切片,以返回集合的子集合。
  • ()操作符允许在底层实现的脚本语言中传递一个脚本表达式。 但是,并不是所有 JSONPath 的实现都支持它。

参见

明确的 JSONPath 文档可在 Goessner 的网站http://goessner.net/articles/JsonPath/获得。 当然,您应该查看您所选择的 JSONPath 实现的文档,以了解具体的实现细节。

Web 上一个方便的是 JSONPath 表达式测试器; http://jsonpath.curiousconcept.com/就是这样一个网站。 通过在测试器中粘贴 JSON 和 JSONPath 表达式,您可以评估 JSONPath 并查看结果是什么。 这是在第一次开始时动态调试 JSONPath 表达式的一种非常简单的方法。 这里有一个例子:

See also

使用 JSONPath 括号符号查询 JSON 文档

JSONPath 提供了一种替代的表示法,括号表示法,它类似于点表示法来查询字段。 这种语法让人想起在关联数组中访问字段的方式,在关联数组中,您将字段名作为选择器传递给operator[]以获得指定字段中的值。

How to do it…

在括号表示法中,我们将前面的食谱示例写如下:

$['store']['book'][0].['title']
$['store']['book'][*].['title']
$['store']..['price']
$..['book'][3]

How it works…

如前所述,第一个示例提取名为 store 的字段中对象中第一本书的标题。 第二个示例提取了商店中所有书籍的所有标题。 第三个示例返回商店中每个商品的所有价格字段的集合,第四个示例返回商店中的第四本书。

使用 JSONPath 脚本构造更复杂的查询

有时,您真正想要做的是查询满足特定条件的所有项,例如,这些项超过了特定的阈值。 JSONPath 提供了?()谓词,该谓词允许您在 JSONPath 中执行单个字段的简单比较脚本。

How to do it…

下面是一个查询所有低于10货币单位的书籍的例子:

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

How it works…

查询首先指定存储中的所有图书项目; 然后,?()谓词使用@选择器选择该类别中的每一项,以获得当前项的值,然后选择<10的 prices。 结果项的标题字段被提取。 该查询将产生以下结果:

[ 
    "Sayings of the Century",
    "Moby Dick"
]

像这样的查询并不适用于 JSONPath 的所有实现。 在http://jsonpath.curiousconcept.com/上检查 JSONPath 表达式测试器,我发现它使用流通信 JSONPath 0.1.1 工作,而不是使用版本 0.8.3 中的 Goessner 的 JSONPath 实现。

任何返回布尔值的表达式都可以在?()谓词中使用。 下面是另一个例子,查询我们集合中小说类的所有书籍:

$.store.book[?(@.category == "fiction")].title

开始都是一样的,那就是挑选所有的书; 它将返回集合中的所有项目,其中一个特定的项目的类别字段等于小说,而不是按价格过滤和退回成本低于10的书籍。

在 web 应用中使用 JSONPath

使用 jsonpathwithjavascript 在您的 web 应用是容易的。 您只需要在应用中包含jsonpath.js实现,然后使用它的jsonPath函数。

准备好

在你开始之前,你需要从https://code.google.com/p/jsonpath/下载 JavaScriptjsonpath库,并使用脚本标签将其包含在你的 HTML 页面使用的脚本中,像这样:

<html>
<head>
<title>…</title>
<script type="text/javascript" src="jsonpath.js"></script>
</head>

jsonPath函数接受一个 JSON 对象(不是字符串,而是 JavaScript 对象),并对内容应用路径操作,返回匹配的值或规范化的路径。 让我们来看一个例子。

How to do it…

下面是一个示例,它从我在介绍中展示的 JSON 对象中返回标题列表:

var o = { /* object from the introduction */ };
var result = jsonPath(o, "$..title");

注意,如果你将对象作为字符串,你必须首先使用JSON.parse来解析它:

var json = "…";
var o = JSON.parse(json);
var result = jsonPath(o, "$..title");

How it works…

前面的代码使用jsonPath函数从当前传递的对象中提取所有标题。 jsonPath函数接受一个 JavaScript 对象、路径和一个可选的结果类型,该类型指示返回值应该是该值的值还是该值的路径。 当然,传入的对象可以是结构化的对象,也可以是数组。

参见

Goessner 关于 JSONPath 的原始实现的原始文档在http://goessner.net/articles/JsonPath/

在 Node.js 中使用 JSONPath

有一个可用的 npm 包,其中包含 JavaScript JSONPath 实现的,所以如果你想从 Node.js 使用 JSONPath,你只需要安装 JSONPath 模块并直接调用它。

准备好

要安装 JSONPath 模块,运行以下命令将该模块包含在当前应用中:

npm install JSONPath

或者,您可以运行以下命令为系统上的所有项目包括它:

npm install –g JSONPath

接下来,你必须在你的源代码中要求这个模块,像这样:

var jsonPath = require('JSONPath');

这将把JSONPath模块加载到您的环境中,并在jsonPath变量中存储一个引用。

How to do it…

Node.js 的 JSONPath 模块定义了一个方法eval,它使用一个 JavaScript 对象和一个路径进行计算。 例如,要获得示例文档中的标题列表,我们需要执行以下代码:

var jsonPath = require('JSONPath');

var o = { /* object from the introduction */ };
var result = jsonPath.eval(o, "$..title");

如果你打算将一个路径应用到 JSON 中字符串形式,请确保首先解析它:

var jsonPath = require('JSONPath');

var json = "…";
var o = JSON.parse(json);
var result = jsonPath.eval(o, "$..title");

How it works…

JSONPath模块的eval方法接受一个 JavaScript 对象(不是一个包含 JSON 的字符串),并应用您传递的路径从对象返回相应的值。

参见

关于 Node.js 的 JSONPath 模块的文档,请参见https://www.npmjs.com/package/JSONPath

在 PHP 应用中使用 JSONPath

在您的 PHP 应用需要使用 JSONPath 你包括 JSONPath PHP 实现可以在 https://code.google.com/p/jsonpath/,和解析 JSON 字符串一个 PHP 混合对象在应用 JSONPath 路径之前你想从jsonPath函数中提取数据。

准备好

您将需要从code.google.comhttps://code.google.com/p/jsonpath/下载jsonpath.php,并将其包括在您的应用的require_once指令。 您还需要确保您的 PHP 实现包含json_decode

How to do it…

下面是一个简单的例子:

<html>
<body>
<pre>
<?php
  require_once('jsonpath.php');
  $json = '…'; // from the introduction to this chapter
  $object = json_decode($json);
  $titles = jsonPath($object, "$..title");
  print($titles);
?>
</pre>
</body>
</html>

How it works…

前面的代码首先需要 PHPJSONPath 实现,该实现定义了jsonPath函数。 然后使用json_decode解码 JSON 字符串,然后提取json_decode返回的混合 PHP 对象中的标题。

jsonPath的 JavaScript 版本类似,PHP 版本接受三个参数:执行提取的对象、提取的路径和指定是返回数据还是返回结构中数据的路径的可选第三个参数。

参见

关于 JSONPath 的 PHP 实现的更多信息,请参见 Stefan Goessner 的网站http://goessner.net/articles/JsonPath/

在 Python 应用中使用 JSONPath

也有一些 JSONPath 的 Python 实现。 最好的是jsonpath-rw库,它提供了语言扩展,因此路径是一流的语言对象。

准备好

你需要使用 pip 安装jsonpath-rw库:

pip install jsonpath-rw

当然,在使用它们时,还需要包含库的必要位:

fromjsonpath_rw import jsonpath, parse

How to do it…

下面是一个简单的例子,使用了我们在引言中的 store 内容存储在变量object中:

>>> object = { … }

>>>path = parse('$..title') 

>>> [match.value for match in path.find(object)]
['Sayings of the Century','Sword of Honour', 'Moby Dick', 'The Lord of the Rings']

How it works…

使用这个库处理路径表达式有点像匹配正则表达式; 解析出 JSONPath 表达式,然后使用 path 的find方法将其应用到要切片的 Python 对象。 这段代码定义了对象,然后创建一个将其存储在 path 中的路径表达式,解析获取所有标题的 JSONPath。 最后,创建一个由传递给路径的对象中的路径找到的值数组。

参见

Python JSONPath 库的文档位于https://pypi.python.org/pypi/jsonpath-rw

在 Java 应用中使用 JSONPath

Java 中也有 JSONPath 的实现,由Jayway编写。 它可以从 GitHub 获得,如果你的项目使用 Maven 构建系统,你也可以通过中央 Maven 库获得。 它匹配原始的 JSONPath API,返回 JSON 对象中字段的 Java 对象和集合。

准备好

你将需要从 GitHub 下载代码https://github.com/jayway/JsonPath,或者,如果你使用 Maven 作为你的构建系统,包括以下依赖:

<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.0.0</version>
</dependency>

How to do it…

Java 实现解析你的 JSON,导出一个 JsonPath类,使用 read 方法读取 JSON,解析它,然后在你通过的路径提取内容:

String json = "...";

List<String>titles = JsonPath.read(json,
"$.store.book[*].title");

How it works…

read 方法解析您传递的 JSON,然后应用您传递的路径从 JSON 中提取值。 如果必须从同一个文档中提取多个路径,最好只解析文档一次,然后在解析后的文档上调用 read,如下所示:

String json = "...";

Object document = 
Configuration.defaultCConfiguration().jsonProvider().parse(json));

List<String>titles = JsonPath.read(document,
  "$.store.book[*].title");
List<String>authors = JsonPath.read(document,
  "$.store.book[*].author");

There's more…

Java JSONPath 库还提供了流畅的语法,其中 read 和其他方法的实现将返回一个上下文,您可以在该上下文上继续调用其他 JSONPath 库方法。 例如,要获得一个价格高于10的图书列表,我还可以执行以下代码:

List<Map<String, Object>>expensiveBooks = JsonPath
                            .using(configuration)
                            .parse(json)
                            .read("$.store.book[?(@.price > 10)]", 
                              List.class);

它使用配置配置JsonPath,解析您传递的 JSON,然后使用路径选择器调用 read,该路径选择器选择所有价格大于10值的图书对象。

Java 中的 JsonPath 库试图将其结果对象转换为您所期望的基本类:列表、字符串等等。 有些路径操作——..?()[number:number:number]——总是返回一个列表,即使结果值是单个对象。

参见

关于 Java JSONPath 实现的文档,请参见https://github.com/jayway/JsonPath

使用 JSONPath 和 SelectToken 来查询 c#应用中的 JSONPath 表达式

如果你使用 Newonsoft 的 Json。 在。NET 环境下,可以使用它的SelectToken实现对 JSON 文档进行 JSONPath 查询。 首先,将 JSON 解析为JObject,然后进行查询。

准备好

您需要包含 Json。 NET 程序集。 为此,按照步骤第七章,使用 JSON 类型安全的方式,在准备部分如何使用 JSON 序列化一个对象。 配方。

How to do it…

下面是如何从介绍中的示例中提取所有书籍的所有标题,并得到第一个结果:

using System;
using System.Collections.Generic;
using System.Linq;
   using Newtonsoft.Json.Linq;

// …

static void Main(string[] args)
{
  var obj = JObject.Parse(json);

  var titles = obj.SelectTokens("$.store.book[*].title");

  Console.WriteLine(titles.First());
} 

How it works…

JObjectSelectTokens方法接受 JSONPath 表达式并将其应用于对象。 这里,我们提取JObject实例的列表,每个匹配项顶级$.store.book一个路径,然后调用Values方法获得强迫每个标题字段的字符串值在每个返回的JObject实例。 当然,原始 JSON 需要解析,我们使用JObject.parse进行解析。

注意,SelectTokens返回一个可枚举集合,您可以使用 LINQ 表达式进一步处理它,就像我们在这里调用First一样。 严格地说,SelectTokens返回IEnumberable<JToken>,其中每个JToken是单个 JSON 集合。 JObject 还提供了SelectToken方法,该方法返回单个实例。

但是,注意不要混淆SelectTokenSelectTokens。 前者只能返回单个JToken,而后者在任何时候都需要在 JSONPath 查询中返回项的集合。

也支持过滤。 例如,要获取《白鲸记》的数据JObject,我可以这样写:

var book = obj.SelectToken(
"$.store.book[?(@.title == 'Moby Dick')]");

这将从store字段中的book集合中选择title匹配“Moby Dick”的文档。

参见

看到文档和例子SelectTokenSelectTokens在杰森 Newton-King 网站 http://james.newtonking.com/archive/2014/02/01/json -网- 6 - 0 -释放- 1% - e2%80%93 jsonpath -和- f -支持,或 Json。 http://www.newtonsoft.com/json/help/html/QueryJsonSelectToken.htm

使用 Json 的 LINQ NET 来在您的 c#应用中查询 JSON

如果你是为。net 开发,你可能想要完全跳过 JSONPath 而使用 Json。 NET 支持基于字段名订阅和 LINQ 支持。 Json。 NET 支持 LINQ 开箱即用,允许您以流畅或语句语法对 JSON 编写任何想要的查询。

准备好

与前面的方法一样,您的. net 项目需要使用 Json.NET。 包含 Json。 网在您的项目中,按照我给你的步骤第七章,使用 JSON 类型安全的方式,在开始部分如何使用 JSON 序列化一个对象。 配方。

How to do it…

您将解析 JSON 为JObject,然后您可以根据得到的JObject计算 LINQ 表达式,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;

static void Main(string[] args)
{
  var obj = JObject.Parse(json);
  var titles = from book in obj["store"]["book"] 
      select (string)book["title"];

  Console.WriteLine(titles.First());
}

当然,因为它是 LINQ,所以也支持流畅的语法:

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;

static void Main(string[] args)
{
  var sum = obj["store"]["book"]
             .Select(x => x["price"])
             .Values<double>().Sum();

  Console.WriteLine(sum);
}

How it works…

第一个示例选择所有title对象,从每个book字段中选择一个,在返回结果之前将每个对象转换为一个字符串。 第二个示例在book的所有price字段上执行选择,将结果值转换为 double,并调用列表上的Sum方法,以获得所有图书的总价格。

需要注意的是 Json 中子字段的通常返回类型。 NET LINQ 查询是JObject,所以在流畅的语法中编写表达式时,必须使用JObject模板的ValueValues方法来获取这些对象的值。 你第一次尝试计算的结果可能是这样的:

var s = obj["store"]["book"].
  Select(x =>x["price"]).Sum();

但是,这不会工作,因为选择的返回值是一个JObjects 的列表,不能直接求和。

提示

当写 LINQ 表达式时,LINQPad(http://www.linqpad.net)尤其有用。 如果你做了大量的 LINQ 和 JSON,投资于 Developer 或 Premium 版本可能是明智的,因为这些版本支持与 NuGet 的集成,让你可以包含 JSON。 NET 在你的测试查询中。

参见

关于 LINQ 和 Json 的更多信息。 NET,参见 Json。 http://www.newtonsoft.com/json/help/html/LINQtoJSON.htm