使用 JSON 架构,如何在将 JSON 解析为 JObject 时过滤掉其他属性?
Using a JSON Schema, how can I filter out additional properties when parsing JSON to a JObject?
我正在尝试仅解析提供的 JSON 的一部分。我正在使用 Newtonsoft.Json.Schema nuget。
对于下一个示例,我只想反序列化名称和年龄属性。
JSchema schema = JSchema.Parse(@"{
'id': 'person',
'type': 'object',
'additionalProperties' : false,
'properties': {
'name': {'type':'string'},
'age': {'type':'integer'}
}
}");
JsonTextReader reader = new JsonTextReader(new StringReader(@"{
'name': 'James',
'age': 29,
'salary': 9000.01,
'jobTitle': 'Junior Vice President'
}"));
JSchemaValidatingReader validatingReader = new JSchemaValidatingReader(reader);
validatingReader.Schema = schema;
JsonSerializer serializer = new JsonSerializer();
JObject data = serializer.Deserialize<JObject>(validatingReader);
如果我设置 'additionalProperties' : true
我会反序列化不必要的字段。
但是如果我设置'additionalProperties' : false
,我会收到一个错误:
Newtonsoft.Json.Schema.JSchemaValidationException: 属性 'salary' 尚未定义,架构不允许附加属性。路径 'salary',第 4 行,位置 11.
请注意,我只会在运行时知道需要的字段。我收到了很大的 JSON,我需要创建一些解决方案来反序列化这个 JSON 的一部分。用户应该决定哪些属性应该被处理,哪些不应该被处理。
我认为您的用例没有任何内置方法,因为 additionalProperties
意味着 forbid/allow 附加属性。但是一旦它们在模式中被允许,它们也会被反序列化。在模式中允许其他属性,但不允许它们出现在数据中对我来说没有多大意义。也许您可以解释一下您的用例?
最简单的可能是反序列化为 class 而不是 JObject
。并且 class 仅定义您希望看到的属性
class Person {
[JsonProperty("name")];
public string Name {get;set;}
[JsonProperty("age")];
public int Age {get;set;}
}
...
Person p = serializer.Deserialize<Person>(validatingReader);
JSchemaValidatingReader
代表一个reader提供JSchema validation.它不提供任何类型的过滤能力。
你可以做的是将你的 JSON 加载到 JToken
中,用 SchemaExtensions.IsValid(JToken, JSchema, out IList<ValidationError>)
, and then remove additional properties at the path indicated by ValidationError.Path
验证。
为此,请按如下方式修改您的代码:
var data = JObject.Parse(jsonString); // The string literal from your question
var isValid = data.IsValid(schema, out IList<ValidationError> errors);
if (!isValid)
{
foreach (var error in errors)
{
if (error.ErrorType == ErrorType.AdditionalProperties)
data.SelectToken(error.Path)?.RemoveFromLowestPossibleParent();
}
}
使用扩展方法:
public static partial class JsonExtensions
{
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
// If the parent is a JProperty, remove that instead of the token itself.
var property = node.Parent as JProperty;
var contained = property ?? node;
if (contained.Parent != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (property != null)
property.Value = null;
return node;
}
}
备注:
当错误类型为ErrorType.AdditionalProperties
时,Path
将直接指向不需要的属性,但对于其他错误类型如ErrorType.Required
该路径可能指向 父容器 。因此,在删除与给定路径上的错误相关的标记之前,您应该检查错误类型。
如果您的 JSON 很大,建议使用 JsonSerializer.CreateDefault().Deserialize<JToken>(reader)
直接从流中反序列化(正如您当前所做的那样)以避免加载 JSON 转换成中间字符串。
演示 fiddle here.
我正在尝试仅解析提供的 JSON 的一部分。我正在使用 Newtonsoft.Json.Schema nuget。 对于下一个示例,我只想反序列化名称和年龄属性。
JSchema schema = JSchema.Parse(@"{
'id': 'person',
'type': 'object',
'additionalProperties' : false,
'properties': {
'name': {'type':'string'},
'age': {'type':'integer'}
}
}");
JsonTextReader reader = new JsonTextReader(new StringReader(@"{
'name': 'James',
'age': 29,
'salary': 9000.01,
'jobTitle': 'Junior Vice President'
}"));
JSchemaValidatingReader validatingReader = new JSchemaValidatingReader(reader);
validatingReader.Schema = schema;
JsonSerializer serializer = new JsonSerializer();
JObject data = serializer.Deserialize<JObject>(validatingReader);
如果我设置 'additionalProperties' : true
我会反序列化不必要的字段。
但是如果我设置'additionalProperties' : false
,我会收到一个错误:
Newtonsoft.Json.Schema.JSchemaValidationException: 属性 'salary' 尚未定义,架构不允许附加属性。路径 'salary',第 4 行,位置 11.
请注意,我只会在运行时知道需要的字段。我收到了很大的 JSON,我需要创建一些解决方案来反序列化这个 JSON 的一部分。用户应该决定哪些属性应该被处理,哪些不应该被处理。
我认为您的用例没有任何内置方法,因为 additionalProperties
意味着 forbid/allow 附加属性。但是一旦它们在模式中被允许,它们也会被反序列化。在模式中允许其他属性,但不允许它们出现在数据中对我来说没有多大意义。也许您可以解释一下您的用例?
最简单的可能是反序列化为 class 而不是 JObject
。并且 class 仅定义您希望看到的属性
class Person {
[JsonProperty("name")];
public string Name {get;set;}
[JsonProperty("age")];
public int Age {get;set;}
}
...
Person p = serializer.Deserialize<Person>(validatingReader);
JSchemaValidatingReader
代表一个reader提供JSchema validation.它不提供任何类型的过滤能力。
你可以做的是将你的 JSON 加载到 JToken
中,用 SchemaExtensions.IsValid(JToken, JSchema, out IList<ValidationError>)
, and then remove additional properties at the path indicated by ValidationError.Path
验证。
为此,请按如下方式修改您的代码:
var data = JObject.Parse(jsonString); // The string literal from your question
var isValid = data.IsValid(schema, out IList<ValidationError> errors);
if (!isValid)
{
foreach (var error in errors)
{
if (error.ErrorType == ErrorType.AdditionalProperties)
data.SelectToken(error.Path)?.RemoveFromLowestPossibleParent();
}
}
使用扩展方法:
public static partial class JsonExtensions
{
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
// If the parent is a JProperty, remove that instead of the token itself.
var property = node.Parent as JProperty;
var contained = property ?? node;
if (contained.Parent != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (property != null)
property.Value = null;
return node;
}
}
备注:
当错误类型为
ErrorType.AdditionalProperties
时,Path
将直接指向不需要的属性,但对于其他错误类型如ErrorType.Required
该路径可能指向 父容器 。因此,在删除与给定路径上的错误相关的标记之前,您应该检查错误类型。如果您的 JSON 很大,建议使用
JsonSerializer.CreateDefault().Deserialize<JToken>(reader)
直接从流中反序列化(正如您当前所做的那样)以避免加载 JSON 转换成中间字符串。
演示 fiddle here.