25

I've combed through the MS docs but cannot find an attribute equivalent to the NewtonSoft JsonPropertyRequired.

What I'm looking for is this:

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

Am I just missing something or does this level of validation not exist in the Microsoft library?

Pang
  • 9,564
  • 146
  • 81
  • 122
THBBFT
  • 1,161
  • 1
  • 13
  • 29
  • There are a lot of features we are acustom to that are not built-in. Check my answer here https://stackoverflow.com/a/58440545/5233410 for an alternative approach – Nkosi Oct 18 '19 at 02:50
  • @Nkosi thanks, I have that already in my service, since I had to write a custom converter in a hurry a few weeks ago. I was just revisiting my initial solution hoping to go the MS way at the inception of the project. Once this gets baked into production it's not likely going to come out. – THBBFT Oct 18 '19 at 04:38
  • A possible way to do this would be with a `JsonConverter` as shown in [this comment](https://github.com/dotnet/corefx/issues/36639#issuecomment-501433729) to [Support for custom converters and OnXXX callbacks #36639](https://github.com/dotnet/corefx/issues/36639) by [steveharter](https://github.com/steveharter). That comment provides a workaround for the lack of `OnDeserialized` events using a `JsonConverter`; you could use the trick there to add validation into the converter. – dbc Nov 28 '19 at 18:01
  • 1
    @dbc - um no, writing *and* maintaining another piece of code is not the answer, using a library that has the functionality is. – THBBFT Nov 28 '19 at 23:08
  • 2
    Official doc: [How to migrate from Newtonsoft.Json to System.Text.Json - Required properties](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#required-properties) – Pang Feb 08 '21 at 02:18

6 Answers6

15

Not as of .NET Core 3.0. The only ones supported are:

JsonConverterAttribute
JsonExtensionDataAttribute
JsonIgnoreAttribute
JsonPropertyNameAttribute

Update: In .NET 5.0 the set is

JsonConstructorAttribute
JsonConverterAttribute
JsonExtensionDataAttribute
JsonIgnoreAttribute
JsonIncludeAttribute
JsonNumberHandlingAttribute
JsonPropertyNameAttribute

Unfortunately even a custom converter with HandleNull => true shown in How to write custom converters for JSON serialization (marshalling) in .NET won't work because if the property in not present Read and Write methods are not called (tested in 5.0, and a modified version in 3.0)

public class Radiokiller
{
    [JsonConverter(typeof(MyCustomNotNullConverter))] 
    public string Name { get; set; }  
}

public class MyCustomNotNullConverter : JsonConverter<string>
{
    public override bool HandleNull => true;

    public override string Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options) =>
        reader.GetString() ?? throw new Exception("Value required.");

    public override void Write(
        Utf8JsonWriter writer,
        string value,
        JsonSerializerOptions options) =>
        writer.WriteStringValue(value);

}
var json = "{}";
var o = JsonSerializer.Deserialize<Radiokiller>(json); // no exception :(

json = "{  \"Name\" : null}";
o = JsonSerializer.Deserialize<Radiokiller>(json); // throws
tymtam
  • 31,798
  • 8
  • 86
  • 126
10

As of 5.0, you can achieve this using constructors. Any exceptions will bubble up during deserialization.

public class Videogame
{
    public Videogame(string name, int? year)
    {
        this.Name = name ?? throw new ArgumentNullException(nameof(name));
        this.Year = year ?? throw new ArgumentNullException(nameof(year));
    }

    public string Name { get; }

    [NotNull]
    public int? Year { get; }
}

N.B. The library won't throw an error if a constructor argument is missing from the JSON. It just uses the default value for the type (so 0 for int). It's a good idea to use nullable value types if you want to handle that scenario.

Also, the types of constructor parameters must match your fields/properties exactly, so no going from int? to int, unfortunately. I've found the analysis attributes [NotNull] and/or [DisallowNull], in the System.Diagnostics.CodeAnalysis namespace, make this less inconvenient.

ryanwebjackson
  • 1,017
  • 6
  • 22
  • 36
Kyle McClellan
  • 664
  • 7
  • 23
  • How exactly does NotNull help? It adds a static code analysis check, correct? – ryanwebjackson Aug 26 '21 at 02:01
  • 1
    @ryanwebjackson sort of. If you have warnings enabled for null handling, `[NotNull]` tells the compiler that null checks aren't needed for this member. `[DisallowNulls]` makes sure it has a value before the constructor finishes. See [documentation](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/nullable-analysis). – Kyle McClellan Aug 27 '21 at 12:55
  • Interesting. I didn't find this information in the Microsoft documentation. Thanks for your explanation. – ryanwebjackson Aug 28 '21 at 13:16
  • Note: For WebAPI you also get 500 errors (with a stack trace) instead of 400 bad request – Alex Apr 05 '22 at 18:30
10

Prependment:

The question was specifically mentioned Required.Always, which in Newtonsoft.Json would require the property and disallow nulls (for instance "firstName": null wouldn't be allowed).

In System.Text.Json that is equivalent to [JsonRequired] (see this article) because it must have a value, and null is not permitted.

The official migration doc is currently very weak on the other related Newtonsoft features. For instance, in Newtonsoft.Json you have the following four options for the Required enumeration (which is the attribute used on JsonProperty):

Default        The property is not required. The default state.
AllowNull      The property must be defined in JSON but can be a null value.
Always         The property must be defined in JSON and cannot be a null value.
DisallowNull   The property is not required but it cannot be a null value.

It's very important to note that this is NOT the [JsonRequired] attribute (from Newtonsoft), whiich means '...always serialize the member, and to require that the member has a value.'.

I'm not going to attempt to make a mapping table for the above because I will almost certainly get it wrong - and I actually don't think it's even possible.

However there is one other related attribute [JsonIgnore(Condition = JsonIgnoreCondition.XXXX])[attribute][3] inSystem.Text.Json` which affects the serialization and is likely to be more useful.

Using [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] will NOT output the value to the JSON if null. So that's sort of similar to Required.AllowNull (but you wouldn't use [JsonRequired] anymore because it isn't actually required!).

The bottom line is there are not exact parallels between the two libraries and the way you use it will influence greatly the ease of your migration. Plan carefully!


.NET 7 (November 2022) now has its very own [JsonRequired] which many will discover for the first time when seeing errors like this:

'JsonRequired' is an ambiguous reference between 'Newtonsoft.Json.JsonRequiredAttribute' and 'System.Text.Json.Serialization.JsonRequiredAttribute'

This will probably be as a result of having the following two using statements in a file:

using System.Text.Json.Serialization;
using Newtonsoft.Json;

The simplest and safest quick solution (if you want to keep with Newtonsoft) is to search and replace and make these two replacements:

[JsonRequired] => [Newtonsoft.Json.JsonRequired]
[JsonRequired( => [Newtonsoft.Json.JsonRequired(

This will only be guaranteed to work if all your code is currently .NET 6 (where usage of this attribute must have been from Newtonsoft). Be careful if you're combining code from a .NET 6 and .NET 7 project. This is what I'm planning on doing to avoid future confusion if I switch (which realistically I don't plan on).

There is a more general article about migrating fully to System.Text.Json if you are able to do that.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • 1
    Note: `JsonRequiredAttribute` works to assert the property is present in the JSON. It doesn't fail if a property is present with a value of `null` (which may also be considered invalid). Other answers may still be valuable to that end. – Kyle McClellan Nov 21 '22 at 18:08
  • @Kyle I'm rediscovering my own answer and actually doing the migration myself. Finding that there is no equivalent to the different values for Required that Newtonsoft has (Requried.AllowNull, Required.Always, Required.DisallowNull) which is sort of annoying because my Typescript client relies on these. – Simon_Weaver Jun 11 '23 at 00:18
  • There's also distinct `[JsonIgnore]` properties too. And make sure if you're using some sort of code generation tool that you don't make any assumptions what attributes it's looking for. For example I'm using NSwag/NJsonSchema which can be configured to use STJ but still looks for some attributes from Newtonsoft. There's no point me explaining here the details - the point being check your assumptions! – Simon_Weaver Jun 12 '23 at 19:08
7

Please try this library I wrote as an extension to System.Text.Json to offer missing features: https://github.com/dahomey-technologies/Dahomey.Json.

You will find support for JsonRequiredAttribute.

public class Videogame
{
    [JsonRequired(RequirementPolicy.Always)]
    public string Name { get; set; }
}

Setup json extensions by calling on JsonSerializerOptions the extension method SetupExtensions defined in the namespace Dahomey.Json. Then deserialize your class with the regular Sytem.Text.Json API.

JsonSerializerOptions options = new JsonSerializerOptions();
options.SetupExtensions();

const string json = @"{""Name"":""BGE2""}";
Videogame obj = JsonSerializer.Deserialize<Videogame>(json, options);
1

In dotnet 6.0 and above IJsonOnDeserialized can be used.

public class Videogame : IJsonOnDeserialized
{
    public string Name { get; set; }

    public string Version { get; set; } // this is optional

    // This is the method coming from IJsonOnDeserialized.
    // If a property is missing in the JSON it will be null (or default).
    // Do the checks and take appropriate action.
    public void OnDeserialized()
    {
        if (Name == null)
        {
            // throw exception
        }
    }
}

Now, in the main code let's try deserializing it.

string validJson1 = "{\"Name\": \"Super Mario\", \"Version\": \"1.1\"}"
JsonSerializer.Deserialize<Videogame>(validJson1); // this works

string validJson2 = "{\"Name\": \"Super Mario\"}"
JsonSerializer.Deserialize<Videogame>(validJson2); // this works too

string invalidJson1 = "{\"Version\": \"1.1\"}"
JsonSerializer.Deserialize<Videogame>(invalidJson1); // this fails

string invalidJson2 = "{}"
JsonSerializer.Deserialize<Videogame>(invalidJson2); // this fails too
bibbsey
  • 969
  • 2
  • 9
  • 14
-5

I am using the generic [Required] attribute that ships in System.ComponentModel.DataAnnotations. I have used it both with Newtonsoft.Json and System.Text.Json.

Daniel Stoyanoff
  • 1,443
  • 1
  • 9
  • 25