65.9K
CodeProject 正在变化。 阅读更多。
Home

Json:一个精小而功能强大的 JSON 引擎

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.46/5 (9投票s)

2019年9月5日

CPOL

7分钟阅读

viewsIcon

8759

downloadIcon

187

使用 JsonPath 和内置的 RPC 支持,轻松与所有 JSON / REST 服务进行通信,或者使用这个小巧的库来操作 JSON。

引言

这是一个小巧的 JSON 库,它提供了一个用于查询的内存对象图和一个用于流处理的拉取解析器。当我不希望在我的代码分发中包含一个功能齐全的 JSON 库时,我就会使用它。它对于许多(如果不是大多数)简单场景来说已经足够了。它不做任何花哨的事情,比如 JSON 模式验证、循环引用解析或对象映射(尽管它支持 C# 中的“dynamic”,可以实现相同的功能)。

它支持 JsonPath。软件的这些部分版权归 (c) 2007 Atif Aziz 所有,保留所有权利;部分版权归 (c) 2007 Stefan Goessner (goessner.net) 所有,均遵循 MIT 许可证。(我没有编写这部分,但这部分做得很好而且很小,所以就用了)。

背景

如果你还不知道,JSON 是一种数据交换格式,主要用于 Web 服务或作为 Web 服务器和 Web 浏览器之间异步通信的数据格式。就这些用途而言,它实际上已经取代了 XML,但这并不是说 XML 仍未被使用或仍然有用。这基本上是一种轻量级、优雅的替代方案。它不包含模式信息,尽管它在某种程度上是自描述且易于解析的。XML 复杂得多,功能也可能更强大,但与 JSON 相比,它也有些臃肿和笨重。此外,JavaScript 可以原生读取 JSON,这对于 Web 开发人员来说是一个巨大的优势。

如果您想了解具体细节,请访问 json.org - 这是一个简短的页面,值得您花时间阅读。

这是一个在 .NET 中处理它的库。

Using the Code

内存对象树的构想

通常,您只需通过实例化 JsonTextReader 并传入一些输入,然后调用 ParseSubtree(),或者通过调用 JsonObject.Parse()JsonObject.LoadFrom(),或者 JsonObject.LoadFromUrl() 来使用它。这将为您提供一个由嵌套列表和字典表示的文档的内存树。

类型映射如下:

  • JSON 对象 { } = IDictionary<string,object> (JsonObject)
  • JSON 数组 [ ] = IList<object> (JsonArray)
  • JSON true/false = System.Boolean
  • JSON null = System.Object (null)
  • JSON (数字) = System.Int32, System.Int64, System.Numerics.BigInteger, System.Double,具体取决于适用的类型
  • JSON 字符串 "..." = System.String
var json = JsonObject.Parse("{\"foo\":\"bar\"}");
json.Add("id", 10);
json["foo"] = "baz";
JsonArray items = new JsonArray();
items.Add("test1");
items.Add("test2");
json["items"] = items;
items.Remove("test2");
Console.WriteLine(json); // pretty print json
return;

JsonObject 和 JsonArray 的概念化

JsonObjectJsonArray 分别是 IDictionary<string,object>IList<object> 的薄包装器,它们具有许多处理 JSON 数据的特定方法,其中大多数方法也作为静态方法包含在内,可操作任何 IDictionary<string,object>IList<object> 实例。就这些静态方法而言,那些也处理其他类型数据(如字符串或其他标量值)的方法也存在于 JsonObject 上。

JsonObject 上,特别值得注意的是

  • Select() - 根据 JsonPath 查询返回一组 Json 元素
  • Get() - 根据一系列键和索引遍历 Json 元素并返回目标
  • CreatePath() - 类似于上面的方法,但如果节点不存在也会创建它们。此方法尚不支持遍历数组
  • Parse() - 解析 JSON 字符串并返回规范化值
  • LoadFrom() - 从指定文件加载 JSON
  • LoadFromUrl() - 从指定 URL 加载 JSON
  • WriteTo() - 将 JSON 写入指定的 TextWriter
  • SaveTo() - 将 JSON 保存到指定文件
  • CopyTo() - 将 JSON 从一个图复制到另一个图
  • Adapt() - 用此包装器包装现有列表,如果尚未包装

JsonArray 上,我们有

  • ToArray<T>() - 将基于 JSON 的数组转换为“真实”数组的各种重载。一种用于标量类型,如数字值;另一种用于自定义类型,如实体包装器。后者接受类型为 Func<object, object> 的创建函数,该函数接受一个 JSON 元素并返回 T。返回值成为该数组的成员。这允许您为数组中的每个项创建一个条目。
  • Adapt() - 用此包装器包装现有列表,如果尚未包装。

请注意,这些类使用值语义,也就是说,如果它们包含相同的数据,则认为它们在逻辑上相等。

无论如何,使用这些功能需要大量的设置才能使其在现实世界中运行,因此请参阅随附的 TMDb 演示项目。

JsonTextReader 的概念化

JsonTextReader 类是一个拉取解析器,允许流式访问 JSON 数据。它的工作方式与 Microsoft 的 XmlReader 非常相似,也就是说,它并不完全友好(拉取解析从来都不是),但也不是很糟糕。除了标准的 Read()NodeTypeValue 成员外,它还包括 ParseSubtree(),它将当前子树作为 JSON object 返回;SkipSubtree(),它快速跳过子树;以及 SkipToField(),它前进到指定字段。如果您需要批量处理大量 JSON 数据,请使用此方法。我不会在这里花费更多时间,因为它几乎不会被任何人直接使用。

使用 JsonRpc 进行 JSON/REST 通信的概念化

JsonRpc 提供了一个主要方法来促进与远程服务器的通信。其签名如下

public static object Invoke(
            string baseUrl, 
            IDictionary<string, object> args = null, 
            object payloadJson = null,
            string timestampField=null, 
            string httpMethod = null,
            Func<object,object> fixupResponse=null, 
            Func<object, object> fixupError=null,
            JsonRpcCacheLevel cache=JsonRpcCacheLevel.Conservative)
{

如您所见,问题在于它需要相当多的参数。让我们来探讨一下。

  • baseUrl - 指示用于进行调用的 URL,不包含任何额外的查询字符串参数。您可以在此 URL 上包含查询字符串参数——如果需要,它们将被追加。
  • args - 如果指定,表示一个简单的 JSON 对象,其中包含所有表示查询字符串参数的标量值。
  • payloadJson - 如果指定,表示要随请求发送的 JSON 有效负载。如果未指定 httpMethod,则此函数将使用 POST 传输有效负载。content-type 设置为 application/json
  • timeStampField - 如果指定,表示根对象下的一个字段,用于添加包含请求日期和时间的时间戳。
  • httpMethod - 如果指定,表示用于请求的自定义 HTTP 方法(如 DELETE)。否则,如果存在有效负载,将使用 GETPOST
  • fixupResponse - 如果指定,表示一个形式为 Func<object,object> 的回调,它接受并返回规范化的 JSON。其目的是允许您对从服务器收到的消息执行后处理。
  • fixupError - 如果指定,表示一个形式为 Func<object,object> 的回调,它接受并返回规范化的 JSON。其目的是允许您在抛出错误消息之前对其执行后处理。这对于确保异常能够返回有意义的信息非常重要。目前,它使用一个简单的启发式方法来查找消息和错误代码。如果找不到,您可以在错误发生之前修复它,并更改字段。
  • cacheLevel - 指示请求要执行的缓存级别。这与 Web 浏览器的缓存非常相似,并且仅缓存 GET 请求,原因很明显。将其设置为 Aggressive 在许多情况下可能会显著减少流量,但可能意味着调用响应可能已过时约 5 分钟左右。如果设置为 Conservative,它每次都会向服务器发送 HEAD 请求,这对于具有某些缓存的 RPC 调用来说是很好的,甚至是适当的,因为它确保您永远不会得到过时的响应。但是,它只真正节省了一些带宽,不一定能节省延迟,尽管使用的 HTTP 流水线应该会缓解这一点。如果设置为 MachinePolicy,它将使用 machine.config 中的设置。

这种方法看起来令人生畏,但实际上您几乎永远不会同时使用所有这些参数。事实上,对于大多数调用,您将传递前两个参数。这是从 themoviedb.org API 检索配置的调用。

var args = new JsonObject();
args.Add("api_key", "c83a68923b7fe1d18733e8776bba59bb");
var json = JsonRpc.Invoke("https://api.themoviedb.org/3/configuration", args);
Console.WriteLine(json);

看到了吗?没那么难。结果是一个 JSON 元素,通常是一个对象 IDictionary<string,object>/JsonObject,有时是一个 IList<object>/JsonArray

欲了解更多信息,请参阅广泛的“演示”应用程序(一个独立的项目)。

历史

  • 2019 年 9 月 5 日 - 初次提交
© . All rights reserved.