.NET JSON Property custom required logic

53 Views Asked by At

Suppose the following class Definition

public class Foo
{
    [JsonProperty]
    public int Number { get; set; }

    [JsonProperty]
    public string Text { get; set; }
}

I now need the property Text to be conditionally required. E.g. when Number > 10 then I need Text to be present in the json string. If Number <= 10 it can be omitted. I cannot simply use the Required Property of the attribute

[JsonProperty(Required = Required.Always)]
public string Text { get;set; }

As this is non conditional. Is there a way I can enforce this? Some examples

{ "Number": 5 } // < valid 
{ "Number": 20 } // < invalid
{ "Number": 20, "Text": "Bar" } // < valid

Our project is using Newtonsoft Json in .NET Framework v4.8 Our classes are serialized using middleware (i.e. we run an API on IIS). We do not manually call JsonConvert.Deserizalize<T>(input) anywhere.

1

There are 1 best solutions below

1
dbc On BEST ANSWER

If you want to enforce that Text is present and non-null when Number is > 10 and require a purely Newtonsoft solution, you could do so in a parameterized constructor:

public class Foo
{
    [JsonConstructor]
    // Make public if you want to enforce the same requirements during regular construction
    protected Foo(int number, string text) 
    {
        if (number > 10 && text == null)
            throw new ArgumentException("missing text");
        this.Number = number;
        this.Text = text;
    }
    
    // Remove if you want to enforce the same requirements during regular construction
    public Foo() { }
    
    [JsonProperty(Required = Required.Always)]
    public int Number { get; set; }

    public string Text { get; set; }
}

Do note that the constructor parameters must have the same names as the corresponding properties, ignoring differences in case.

Demo fiddle #1 here.

Alternatively, if you don't like using a constructor for some reason, you could do the check in an [OnDeserialized] callback:

public class Foo
{
    [JsonProperty(Required = Required.Always)]
    public int Number { get; set; }

    public string Text { get; set; }
    
    [System.Runtime.Serialization.OnDeserialized]
    void OnDeserializedMethod(System.Runtime.Serialization.StreamingContext context)
    {
        if (Number > 10 && Text == null)
        {
            throw new JsonSerializationException("missing text");
        }
    }
}

Demo fiddle #2 here.

Note that both solutions treat a null value for Text as equivalent to a missing value. If you want {"Number":20, "Text":null} to be valid you will need to adopt a more complex solution, such as remembering whether Text was ever set and checking that in the [OnDeserialized] callback:

public class Foo
{
    [JsonProperty(Required = Required.Always)]
    public int Number { get; set; }

    string text;
    bool textSpecified;
    public string Text { get => text; set { text = value; textSpecified = true; } }
    
    [System.Runtime.Serialization.OnDeserialized]
    void OnDeserializedMethod(System.Runtime.Serialization.StreamingContext context)
    {
        if (Number > 10 && !textSpecified)
        {
            throw new JsonSerializationException("missing text");
        }
    }
}

Demo fiddle #3 here.