c#中如何对这个json数据进行筛选?

json数据如下: { "R001" : { "A" : false, "B" : false },"R002" : { "A" : false, "…
关注者
8
被浏览
4,435

3 个回答

是要找B等于true的上层节点名称吗,试试 fastCSharp JSON 数据序列化 - C# 高性能自动化服务端框架 - 凹凸架构

           Console.WriteLine(new fastCSharp.setup.cSharp.json.parser().Parse(json).Dictionary
                .firstOrDefault(node => node.Value.Dictionary["B"].Bool)
                .Key.ToString());

找所有B等于true的上层节点名称

           foreach (var node in new fastCSharp.setup.cSharp.json.parser().Parse(json).Dictionary
                 .getFindArray(node => node.Value.Dictionary["B"].Bool))
                Console.WriteLine(node.Key.ToString());

找所有value等于true的上层节点名称与KEY名称集合

           foreach (var node in new fastCSharp.setup.cSharp.json.parser().Parse(json).Dictionary
                .getArray(node => new { Key = node.Key, Nodes = node.Value.Dictionary.getFind(key => key.Value.Bool) })
                .getFindArray(node => node.Nodes.Length != 0))
                Console.WriteLine(node.Key.ToString() + "[" + node.Nodes.ToArray().joinString(',', key => key.Key.ToString()) + "]");

Working with JSON

JSON 已成为 XML 的流行替代品。虽然它缺少 XML 的高级功能(例如名称空间、前缀和模式),但它的好处是简单和整洁,其格式类似于将 JavaScript 对象转换为字符串所获得的格式。

过去,.NET 没有对 JSON 的内置支持,您必须依赖第三方库——主要是 Json.NET 。尽管情况已不再如此,但 Json.NET 库仍然很受欢迎,原因有很多:

  • 自 2011 年以来一直存在。
  • 相同的 API 也可以在较旧的 .NET 平台上运行。
  • 它被认为比 Microsoft JSON API 更实用(至少在过去如此)。

Microsoft JSON API 的优势是从头开始设计为简单且极其高效。此外,从 .NET 6 开始,它们的功能已变得非常接近 Json.NET

在本节中,我们将介绍以下内容:

  • 只进读取器和写入器(Utf8JsonReader 和 Utf8JsonWriter)
  • JsonDocument 只读 DOM 读取器
  • JsonNode 读/写 DOM 读取器/写入器

在线补充 albahari.com/nutshell 的 Serialization 一节,我们介绍了 JsonSerializer,它自动将 JSON 序列化和反序列化为类。

Utf8JsonReader

System.Text.Json.Utf8JsonReader 是针对 UTF-8 编码的 JSON 文本的优化的只读阅读器。从概念上讲,它类似于本章前面介绍的 XmlReader,并且使用方式也大致相同。

考虑以下名为 people.json 的 JSON 文件:

{
	"FirstName":"Sara",
	"LastName":"Wells",
	"Age":35,
	"Friends":["Dylan","Ian"]
}

大括号表示一个 JSON 对象(其中包含“名字”和“姓氏”等属性),而方括号表示一个 JSON 数组(其中包含重复元素)。在这种情况下,重复元素是字符串,但它们可以是对象(或其他数组)。

以下代码通过枚举文件的 JSON 标记来解析文件。标记是对象的开头或结尾、数组的开头或结尾、属性的名称或数组或属性值(字符串、数字、真、假或空):

byte[] data = File.ReadAllBytes ("people.json");
Utf8JsonReader reader = new Utf8JsonReader (data);
while (reader.Read())
    switch (reader.TokenType)
        case JsonTokenType.StartObject:
	    Console.WriteLine ($"Start of object");
	    break;
	case JsonTokenType.EndObject:
	     Console.WriteLine ($"End of object");
	    break;
	case JsonTokenType.StartArray:
	    Console.WriteLine();
	    Console.WriteLine ($"Start of array");
	    break;
	case JsonTokenType.EndArray:
	    Console.WriteLine ($"End of array");
	    break;
	case JsonTokenType.PropertyName:
	    Console.Write ($"Property: {reader.GetString()}");
	    break;
	case JsonTokenType.String:
	    Console.WriteLine ($" Value: {reader.GetString()}");
	    break;
	case JsonTokenType.Number:
	    Console.WriteLine ($" Value: {reader.GetInt32()}");
	    break;
	default:
	    Console.WriteLine ($"No support for {reader.TokenType}");
	    break;

这是输出:

Start of object
Property: FirstName Value: Sara
Property: LastName Value: Wells
Property: Age Value: 35
Property: Friends
Start of array
	Value: Dylan
	Value: Ian
End of array
End of object

因为 Utf8JsonReader直接与 UTF-8 一起工作,它无需首先将输入转换为 UTF-16(.NET 字符串的格式)即可遍历标记。仅当您调用诸如 GetString() 之类的方法时,才会转换为 UTF-16。

有趣的是,Utf8JsonReader 的构造函数不接受字节数组,而是接受 ReadOnlySpan<byte>(因此,Utf8JsonReader 被定义为 ref 结构)。您可以传入字节数组,因为存在从 T[] 到 ReadOnlySpan<T> 的隐式转换。在第 23 章中,我们描述了 span 是如何工作的,以及如何使用它们通过最小化内存分配来提高性能。

JsonReaderOptions

默认情况下,Utf8JsonReader 要求 JSON 严格符合 JSON RFC 8259 标准。您可以通过传递一个JsonReaderOptions 的实例到 Utf8JsonReader 构造函数指示 Reader 更宽容 。选项允许以下内容:

C-Style comments

默认情况下,JSON 中的注释会导致抛出 JsonException。设置 JsonCommentHandling.Skip 的 CommentHandling 属性导致注释 comments 被忽略,而 JsonCommentHandling.Allow 会导致Reader 识别它们并在它们出现时发出 JsonTokenType.Comment 标记 token 。评论不能出现在其他标记的中间。

Trailing commas

根据标准,对象的最后一个属性和数组的最后一个元素不得有尾随逗号。将AllowTrailingCommas 属性设置为 e 放宽了这个限制。

Control over the maximum nesting depth

默认情况下,对象和数组可以嵌套到 64 层。将 MaxDepth 设置为不同的数字会覆盖此设置。

Utf8JsonWriter

System.Text.Json.Utf8JsonWriter 是一个只进的 JSON 编写器。它支持以下类型:

  • String 和 DateTime(格式为 JSON 字符串)
  • 数值类型 Int32、UInt32、Int64、UInt64、Single、Double 和 Decimal(格式为 JSON 数字
  • bool(格式为JSON true/false 文字)
  • JSON null
  • 数组

您可以根据 JSON 标准将这些数据类型组织到对象中。它还允许您编写注释,这些注释不是 JSON 标准的一部分,但在实践中通常由 JSON 解析器支持。

下面的代码演示了它的使用:

var options = new JsonWriterOptions { Indented = true };
using (var stream = File.Create ("MyFile.json"))
using (var writer = new Utf8JsonWriter (stream, options))
    writer.WriteStartObject();
    // Property name and value specified in one call
    writer.WriteString ("FirstName", "Dylan");
    writer.WriteString ("LastName", "Lockwood");
    // Property name and value specified in separate calls
    writer.WritePropertyName ("Age");
    writer.WriteNumberValue (46);
    writer.WriteCommentValue ("This is a (non-standard) comment");
    writer.WriteEndObject();

这会生成以下输出文件:

{
	"FirstName": "Dylan",
	"LastName": "Lockwood",
	"Age": 46
	/*This is a (non-standard) comment*/
}

从 .NET 6 开始,Utf8JsonWriter 有一个 WriteRawValue 方法,可以将字符串或字节数组直接发送到 JSON 流中。这在特殊情况下很有用——例如,如果您希望写入的数字始终包含小数点(1.0 而不是 1)。

在此示例中,我们将 JsonWriterOptions 上的 Indented 属性设置为 true 以提高可读性。如果我们不这样做,输出将如下所示:

{"FirstName":"Dylan","LastName":"Lockwood","Age":46...}

JsonWriterOptions 还有一个 Encoder 属性来控制字符串的转义,以及一个 SkipValidation 属性来允许绕过结构验证检查(允许发出无效输出 JSON)。

JsonDocument

System.Text.Json.JsonDocument 将 JSON 数据解析为由按需生成的 JsonElement 实例组成的只读 DOM。与 Utf8JsonReader 不同,JsonDocument 允许您随机访问元素。

JsonDocument 是用于处理 JSON 的两个基于 DOM 的 API 之一,另一个是 JsonNode(我们将在下一节中介绍)。 JsonNode是在.NET 6中引入的,主要是为了满足可写DOM的需求。然而,它也适用于只读场景,并公开了一个更流畅的接口,由使用 JSON 值、数组和对象类的传统 DOM 支持。相比之下,JsonDocument 非常轻量级,仅包含一个注释类(JsonDocument)和两个按需解析底层数据的轻量级结构体(JsonElement 和 JsonProperty)。区别如图 11-1 所示。

<aside> JsonDocument 通过使用池化内存来最大限度地减少垃圾收集,进一步提高了效率。这意味着您必须在使用后处理 JsonDocument;否则,它的内存将不会返回到池中。因此,当类在字段中存储 JsonDocument 时,它还必须实现 IDisposable。如果这很麻烦,请考虑改用 JsonNode。

</aside>

静态 Parse 方法从流、字符串或内存缓冲区实例化 JsonDocument:

using JsonDocument document = JsonDocument.Parse (jsonString);

调用 Parse 时,您可以选择提供 JsonDocumentOptions 对象来控制尾随逗号、注释和最大嵌套深度的处理(有关这些如何实现的讨论选项起作用,请参阅第 546 页的“JsonReaderOptions”)。

从那里,您可以通过 RootElement 属性访问 DOM:

using JsonDocument document = JsonDocument.Parse ("123");
JsonElement root = document.RootElement;
Console.WriteLine (root.ValueKind); // Number

JsonElement 可以表示 JSON 值(字符串、数字、true/false、null)、数组或对象; 通过ValueKind 属性指示它们。

如果元素不是预期的类型,我们在以下部分中描述的方法将抛出异常。如果您不确定 JSON 文件的架构,您可以通过首先检查 ValueKind(或使用 TryGet* 方法)来避免此类异常。

JsonElement 还提供了两种适用于任何类型元素的方法:GetRawText() 返回内部 JSON,WriteTo 将该元素写入 Utf8JsonWriter。

Reading simple values

如果元素表示一个 JSON 值,则可以通过调用 GetString、GetInt32、GetBoolean 等获取其值:

using JsonDocument document = JsonDocument.Parse ("123");
int number = document.RootElement.GetInt32();

JsonElement 还提供了将 JSON 字符串解析为其他常用 CLR 类型的方法,例如 DateTime(甚至是 base-64 二进制) )。还有一些 TryGet* 版本可以避免在解析失败时抛出异常。

Reading JSON arrays

如果 JsonElement 表示一个数组,您可以调用以下方法:

EnumerateArray()

枚举 JSON 数组的所有子项(作为 JsonElements)。

GetArrayLength()

返回数组中元素的数量。

您还可以使用索引器返回特定位置的元素:

using JsonDocument document = JsonDocument.Parse (@"[1, 2, 3, 4, 5]");
int length = document.RootElement.GetArrayLength(); // 5
int value = document.RootElement[3].GetInt32(); // 4

Reading JSON objects

如果元素表示 JSON 对象,则可以调用以下方法:

EnumerateObject()

枚举对象的所有属性名称和值。

GetProperty (string propertyName)

按名称获取属性(返回另一个 JsonElement)。如果名称不存在则抛出异常。

TryGetProperty (string propertyName, out JsonElement value)

返回对象的属性(如果存在)。

例如:

using JsonDocument document = JsonDocument.Parse (@"{ ""Age"": 32}");
JsonElement root = document.RootElement;
int age = root.GetProperty ("Age").GetInt32();

以下是我们如何“发现”Age 属性:

JsonProperty ageProp = root.EnumerateObject().First();
string name = ageProp.Name; // Age
JsonElement value = ageProp.Value;
Console.WriteLine (value.ValueKind); // Number
Console.WriteLine (value.GetInt32()); // 32

JsonDocument and LINQ

JsonDocument 非常适合 LINQ。给定以下 JSON 文件:

[
		"FirstName":"Sara",
		"LastName":"Wells",
		"Age":35,
		"Friends":["Ian"]
		"FirstName":"Ian",
		"LastName":"Weems",
		"Age":42,
		"Friends":["Joe","Eric","Li"]
		"FirstName":"Dylan",
		"LastName":"Lockwood",
		"Age":46,
		"Friends":["Sara","Ian"]
]

我们可以使用 JsonDocument 通过 LINQ 查询它,如下所示:

using var stream = File.OpenRead (jsonPath);
using JsonDocument document = JsonDocument.Parse (json);
var query =
	    from person in document.RootElement.EnumerateArray()
	    select new
	        FirstName = person.GetProperty ("FirstName").GetString(),
		Age = person.GetProperty ("Age").GetInt32(),
		Friends =
			  from friend in person.GetProperty ("Friends").EnumerateArray()
			  select friend.GetString()

因为 LINQ 查询是惰性计算的,所以在文档超出范围之前枚举查询很重要,并且 JsonDocument 通过 using 语句隐式处理。

Making updates with a JSON writer

尽管 JsonDocument 是只读的,但您可以使用 WriteTo 方法将 JsonElement 的内容发送到 Utf8JsonWriter。这提供了一种用于发出 JSON 的修改版本的机制。以下是我们如何从前面的示例中获取 JSON 并将其写入一个新的 JSON 文件,该文件只包含有两个或更多朋友的人:

using var json = File.OpenRead (jsonPath);
using JsonDocument document = JsonDocument.Parse (json);
var options = new JsonWriterOptions { Indented = true };
using (var outputStream = File.Create ("NewFile.json"))
using (var writer = new Utf8JsonWriter (outputStream, options))
    writer.WriteStartArray();
    foreach (var person in document.RootElement.EnumerateArray())
        int friendCount = person.GetProperty ("Friends").GetArrayLength();
	if (friendCount >= 2)
	    person.WriteTo(writer);

但是,如果您需要更新 DOM 的能力,JsonNode 是更好的解决方案。

JsonNode

JsonNode(在 System.Text.Json.Nodes 中)是在 .NET 6 中引入的,主要是为了满足对可写 DOM 的需求。然而,它也适用于只读场景,并公开了一个更流畅的接口,由使用 JSON 值、数组和对象类的传统 DOM 支持(见图 11-1)。作为类,它们会产生垃圾收集成本,但这在大多数真实场景中可能可以忽略不计。 JsonNode 仍然是高度优化的,当重复读取相同的节点时实际上可以比 JsonDocument 更快(因为 JsonNode 虽然懒惰,但会缓存解析结果)。

静态 Parse 方法从流、字符串、内存缓冲区或 Utf8JsonReader 创建 JsonNode:

JsonNode node = JsonNode.Parse (jsonString);

调用 Parse 时,您可以选择提供 JsonDocumentOptions 对象来控制尾随逗号、注释和最大嵌套深度的处理(有关讨论这些选项如何工作,请参阅第 546 页的“JsonReaderOptions”)。与 JsonDocument 不同,JsonNode 不需要处置 disposal

在 JsonNode 上调用 ToString() 会返回人类可读(缩进)的 JSON 字符串。还有一个 ToJsonString() 方法,它返回一个紧凑的 JSON 字符串。

Parse 返回 JsonNode 的子类型,它将是 JsonValue、JsonObject 或 JsonArray。为了避免向下转型的混乱,JsonNode 提供了名为 AsValue()、AsObject() 和 AsArray() 的辅助方法:

var node = JsonNode.Parse ("123"); // Parses to a JsonValue
int number = node.AsValue().GetValue<int>();
// Shortcut for ((JsonValue)node).GetValue<int>();

但是,您通常不需要调用这些方法,因为最常用的成员都暴露在 JsonNode 上类本身:

var node = JsonNode.Parse ("123");
int number = node.GetValue<int>();
// Shortcut for node.AsValue().GetValue<int>();

Reading simple values

我们刚刚看到您可以通过使用类型参数调用 GetValue 来提取或解析一个简单的值。为了使这更容易,JsonNode 重载了 C# 的显式强制转换运算符,启用了以下快捷方式:

var node = JsonNode.Parse ("123");
int number = (int) node;

适用于此的类型包括标准数字类型:char、bool、DateTime、DateTimeOffset 和 Guid(及其可为 null 的版本),以及 string 。

如果不确定解析是否成功,则需要如下代码:

if (node.AsValue().TryGetValue<int>(out var number))
    Console.WriteLine (number);
从 JSON 文本解析的节点在内部由 JsonElement(JsonDocument 只读 JSON API 的一部分)支持。您可以提取底层 JsonElement,如下所示: JsonElement je = node.GetValue<JsonElement>(); 但是,当节点被显式实例化时,这不起作用(当我们更新 DOM 时就是这种情况)。此类节点不是由 JsonElement 支持,而是由实际解析值支持(请参阅第 554 页的“使用 JsonNode 进行更新”)。

Reading JSON arrays

表示 JSON 数组的 JsonNode 将是 JsonArray 类型。

JsonArray 实现了 IList<JsonNode>,因此您可以枚举它并像访问数组或列表一样访问元素:

var node = JsonNode.Parse (@"[1, 2, 3, 4, 5]");
Console.WriteLine (node.AsArray().Count); // 5
foreach (JsonNode child in node.AsArray())
{ ... }

作为快捷方式,您可以直接从 JsonNode 类访问索引器:

Console.WriteLine ((int)node[0]); // 1

Reading JSON objects

表示 JSON 对象的 JsonNode 将是类型 JsonObject。

JsonObject 实现 IDictionary<string, JsonNode>,因此您可以通过索引器访问成员,以及枚举字典的键/值对。

与 JsonArray 一样,您可以直接从 JsonNode 类访问索引器:

var node = JsonNode.Parse (@"{ ""Name"":""Alice"", ""Age"": 32}");
string name = (string) node ["Name"]; // Alice
int age = (int) node ["Age"]; // 32

以下是我们如何“发现” Name 和 Age 属性:

// Enumerate over the dictionary’s key/value pairs:
foreach (KeyValuePair<string,JsonNode> keyValuePair in node.AsObject())
    string propertyName = keyValuePair.Key; // "Name" (then "Age")
    JsonNode value = keyValuePair.Value;

如果您不确定是否已定义属性,以下模式也适用:

if (node.AsObject().TryGetPropertyValue("Name", out JsonNode nameNode))
{ ... }

Fluent traversal and LINQ

您可以仅使用索引器深入到层次结构。例如,给定以下 JSON 文件:

[
		"FirstName":"Sara",
		"LastName":"Wells",
		"Age":35,
		"Friends":["Ian"]
		"FirstName":"Ian",
		"LastName":"Weems",
		"Age":42,
		"Friends":["Joe","Eric","Li"]
		"FirstName":"Dylan",
		"LastName":"Lockwood",
		"Age":46,
		"Friends":["Sara","Ian"]
]

我们可以提取第二个人的第三个朋友,如下所示:

string li = (string) node[1]["Friends"][2];

这样的文件也很容易通过 LINQ 查询:

JsonNode node = JsonNode.Parse (File.ReadAllText (jsonPath));
var query =
	    from person in node.AsArray()
	    select new
	        FirstName = (string) person ["FirstName"],
		Age = (int) person ["Age"],
		Friends = from friend in person ["Friends"].AsArray()
			  select (string) friend

与 JsonDocument 不同,JsonNode 不是一次性的,因此我们不必担心潜在的用于惰性枚举期间的处置 disposal

Making updates with JsonNode

JsonObject 和 JsonArray 是可变的,因此您可以更新它们的内容。

向 JsonObject 替换或添加属性的最简单方法是通过索引器。在下面的示例中,我们将 Color 属性的值从“Red”更改为“White”并添加一个名为“Valid”的新属性:

var node = JsonNode.Parse ("{ \\"Color\\": \\"Red\\" }");
node ["Color"] = "White";
node ["Valid"] = true;
Console.WriteLine (node.ToJsonString()); // {"Color":"White","Valid":true}

该示例中的第二行是以下内容的快捷方式:

node ["Color"] = JsonValue.Create ("White");

而不是为属性分配一个简单的值,您可以为它分配一个 JsonArray 或 JsonObject。 (我们将在下一节演示如何构造 JsonArray 和 JsonObject 实例。)

要删除一个属性,首先转换为 JsonObject(或调用 AsObject),然后调用 Remove 方法:

node.AsObject().Remove ("Valid");

(JsonObject 还暴露了一个 Add 方法,该方法会抛出异常如果该属性已存在。)

JsonArray 还允许您使用索引器替换项目:

var node = JsonNode.Parse ("[1, 2, 3]");
node[0] = 10;

调用 AsArray 公开 Add/Insert/Remove/RemoveAt 方法。在下面的示例中,我们删除数组中的第一个元素并在末尾添加一个:

var arrayNode = JsonNode.Parse ("[1, 2, 3]");
arrayNode.AsArray().RemoveAt(0);
arrayNode.AsArray().Add (4);
Console.WriteLine (arrayNode.ToJsonString()); // [2,3,4]

Constructing a JsonNode DOM programmatically

JsonArray 和 JsonObject 具有支持对象初始化语法的构造函数,这允许您在一个表达式中构建整个 JsonNode DOM:

var node = new JsonArray
    new JsonObject {
                        ["Name"] = "Tracy",
		        ["Age"] = 30,
			["Friends"] = new JsonArray ("Lisa", "Joe")
		new JsonObject {
			["Name"] = "Jordyn",
			["Age"] = 25,
			["Friends"] = new JsonArray ("Tracy", "Li")

这将计算为以下 JSON:

[
		"Name": "Tracy",
		"Age": 30,
		"Friends": ["Lisa", "Joe"]