1

I am trying to parse only part of provided JSON. I am using Newtonsoft.Json.Schema nuget. For the next example, I want to deserialize only name and age properties.

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);

If I will set 'additionalProperties' : true I will get unnecessary fields deserialized.

enter image description here

But if I will set 'additionalProperties' : false, I will receive an error:

Newtonsoft.Json.Schema.JSchemaValidationException: Property 'salary' has not been defined and the schema does not allow additional properties. Path 'salary', line 4, position 11.

Note that I will know the needed fields only in runtime. I receive big JSON and I need to create some solution to deserialize only part of this JSON. And users should decide which properties should be processed and which aren't.

dbc
  • 104,963
  • 20
  • 228
  • 340
Hanna Holasava
  • 192
  • 1
  • 15
  • Not sure this works, but try `'allowAdditionalProperties': true` instead of `'additionalProperties': true`. – Thomas May 24 '21 at 12:52
  • @Thomas If I will use `'allowAdditionalProperties' : false`, It still will parse salary and JobTitle properties. But I don't want to see them in the result – Hanna Holasava May 24 '21 at 12:57

2 Answers2

2

JSchemaValidatingReader Represents a reader that provides JSchema validation. It does not provide any sort of filtering capability.

What you could do instead is to load your JSON into a JToken, validate with SchemaExtensions.IsValid(JToken, JSchema, out IList<ValidationError>), and then remove additional properties at the path indicated by ValidationError.Path.

To do this, modify your code as follows:

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();
    }
}

using the extension method:

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;
    }
}

Notes:

  • When the error type is ErrorType.AdditionalProperties the Path will point directly to the unwanted property, but for other error types such as ErrorType.Required the path may point to the parent container. Thus you should check the error type before removing a token related to an error at a given path.

  • If your JSON is large, it is recommended to deserialize directly from a stream using JsonSerializer.CreateDefault().Deserialize<JToken>(reader) (as you are doing currently) to avoid loading the JSON into an intermediate string.

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
0

I don't think there is any built-in method for your usecase, because the additionalProperties is meant to either forbid/allow additional properties. But once they are allowed in the schema, they are also deserialized. It doesn't make much sense to me to allow additional properties in the schema, but then don't allow them to show up in the data. Maybe you can explain your usecase?

The simplest would probably be to deserialize to a class instead of JObject. And in that class only define the properties you would like to see

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

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

...

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

derpirscher
  • 14,418
  • 3
  • 18
  • 35
  • This is my main problem, I will know needed fields only in runtime. I receive big JSON and I need to create some solution to deserialize only part of this JSON. And users should decide which properties should be processed and which aren't. Maybe you have some other solutions instead of using JSchema? – Hanna Holasava May 24 '21 at 13:36
  • Well, you could of course create the type with your required properties at runtime like sketched here https://stackoverflow.com/questions/929349/is-there-a-way-to-build-a-new-type-during-runtime Or you could do a postprocessing of your `data` object after deserialization and remove all unwanted properties. But I don't think there is any possiblity to just "partially" deserialize a json string. – derpirscher May 24 '21 at 13:45