使用 JSON Schema,在将 JSON 解析为 JObject 时如何过滤掉其他属性?

问题描述

我试图仅解析提供的 JSON 的一部分。我正在使用 Newtonsoft.Json.Schema nuget。 对于下一个示例,我只想反序列化 name 和 age 属性

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,我会反序列化不必要的字段。

enter image description here

但是如果我设置 'additionalProperties' : false,我会收到一个错误

Newtonsoft.Json.Schema.JSchemaValidationException:尚未定义属性“薪水”,并且架构不允许附加属性。路径 'salary',第 4 行,位置 11。

请注意,我只会在运行时知道所需的字段。我收到了很大的 JSON,我需要创建一些解决方案来反序列化这个 JSON 的一部分。并且用户应该决定哪些属性应该被处理,哪些不应该被处理。

解决方法

JSchemaValidatingReader 代表提供 JSchema 验证的阅读器。它不提供任何类型的过滤功能。

您可以做的是将您的 JSON 加载到 JToken 中,使用 SchemaExtensions.IsValid(JToken,JSchema,out IList<ValidationError>) 进行验证,然后在 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 加载到中间字符串中。

演示小提琴here

,

我认为您的用例没有任何内置方法,因为 additionalProperties 旨在禁止/允许其他属性。但是一旦它们在模式中被允许,它们也会被反序列化。在模式中允许其他属性对我来说没有多大意义,但不允许它们显示在数据中。也许您可以解释一下您的用例?

最简单的方法可能是反序列化为一个类而不是 JObject。在那个类中只定义你想看到的属性

class Person {
  [JsonProperty("name")];
  public string Name {get;set;}

  [JsonProperty("age")];
  public int Age {get;set;}
}

...

Person p = serializer.Deserialize<Person>(validatingReader);