Search code examples
c#asp.net-web-apijson.netnjsonschema

check for duplicate properties in JSON


We are working on a .Net core based web api application and for this we have a requirement to validate incoming request body which is JSON format against the c# based type. We are at this point evaluating NJsonSchema library to see if it throws duplicate property error. But looks like it doesnt support this validation. We also checked JSON schema validator from NewtonSoft but seems like it doesnt support duplicate property validations either.

Below is the minimized code using NJsonSchema that we use -

using NewtonSoft.Json;
public class MyRequest
    {
        [JsonRequired]
        [JsonProperty("name")]
        public string Name { get; set; }
}

and when we pass a JSON object like this -

{"name":"abc","name":"xyz"}

We need our JSON validator to throw error for duplicate property Our example test looks like this -

[Test]
        public async System.Threading.Tasks.Task SchemaValidation_WithDuplicateProperty_Async()
        {
            var jsonString = await File.ReadAllTextAsync("Data//JsonWithDuplicateProperty.json");
            var schema = JsonSchema.FromType<MyRequest>();
            var errors = schema.Validate(jsonString);
            Assert.That(errors.Count(), Is.EqualTo(1));
        }

So my question - Has anyone done this in the past? Or are there any libraries for .net core that provides JSON validation for duplicate properties and/or can this be done using NJsonSchema or NewtonSoft.


Solution

  • As @zaggler notes, using Newtonsoft, you can use the DuplicatePropertyNameHandling enum. Unfortunately, you can't use it directly in a a call to DeserializeObject (in the JsonSerializerSettings); it has to be used in a JToken Reader. See this discussion thread for more details:

    https://github.com/JamesNK/Newtonsoft.Json/issues/931

    Here is a method that wraps the action up in a DeserializeObject-esque way:

    using System;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    using System.IO;
                        
    public class Program
    {
        public static void Main()
        {
            var json = @"{""name"":""abc"",""name"":""xyz""}";
    
            var objA = DeserializeObject<MyRequest>(json, new JsonSerializerSettings(), DuplicatePropertyNameHandling.Ignore);
            Console.WriteLine(".Ignore: " + objA.Name);
            
            var objB = DeserializeObject<MyRequest>(json, new JsonSerializerSettings(), DuplicatePropertyNameHandling.Replace);
            Console.WriteLine(".Replace: " + objB.Name);
            
            var objC = DeserializeObject<MyRequest>(json, new JsonSerializerSettings(), DuplicatePropertyNameHandling.Error);
            Console.WriteLine(".Error: " + objC.Name); // throws error before getting here
    }
        public static T DeserializeObject<T>(string json, JsonSerializerSettings settings, DuplicatePropertyNameHandling duplicateHandling)
        {
            JsonSerializer jsonSerializer = JsonSerializer.CreateDefault(settings);
            using (var stringReader = new StringReader(json))
            using (var jsonTextReader = new JsonTextReader(stringReader))
            {
                jsonTextReader.DateParseHandling = DateParseHandling.None;
                JsonLoadSettings loadSettings = new JsonLoadSettings { 
                    DuplicatePropertyNameHandling = duplicateHandling
                };
                var jtoken = JToken.ReadFrom(jsonTextReader, loadSettings);
                return jtoken.ToObject<T>(jsonSerializer);
            }
        }
    }
    public class MyRequest
        {
            [JsonRequired]
            [JsonProperty("name")]
            public string Name { get; set; }
    }
    

    Output:

    .Ignore: abc

    .Replace: xyz

    Run-time exception (line 31): Property with the name 'name' already exists in the current JSON object. Path 'name', line 1, position 21.

    See:

    https://dotnetfiddle.net/EfpzZu