使用 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.