2

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.

user979189
  • 1,230
  • 2
  • 15
  • 39
  • 1
    The funny thing about this is it's legal according to [RFC7159](https://datatracker.ietf.org/doc/html/rfc7159#section-4) . It say's: *The names within an object SHOULD be unique.* Also worth mentioning, [JamesNK](https://github.com/JamesNK/Newtonsoft.Json/issues/931) says "There is no way in Json.NET to restrict them. It will use the last value.", interesting question BTW. – Trevor Jun 10 '21 at 20:04
  • 1
    Looking a little further it seems using the [JsonLoadSettings](https://www.google.com/search?q=JsonLoadSettings) may be helpful and specifically setting the `DuplicatePropertyNameHandling` [seems to throw](https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_Linq_JsonLoadSettings_DuplicatePropertyNameHandling.htm) in cases like this. – Trevor Jun 10 '21 at 20:11

1 Answers1

1

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

Jonathan
  • 4,916
  • 2
  • 20
  • 37