Does the new `System.Text.Json` have a required property attribute?
Asked Answered
S

6

28

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?

Softwood answered 18/10, 2019 at 2:36 Comment(5)
There are a lot of features we are acustom to that are not built-in. Check my answer here https://mcmap.net/q/418070/-enum-type-no-longer-working-in-net-core-3-0-frombody-request-object for an alternative approachBiblioclast
@Biblioclast 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.Softwood
A possible way to do this would be with a JsonConverter as shown in this comment to Support for custom converters and OnXXX callbacks #36639 by 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.Audry
@Audry - um no, writing and maintaining another piece of code is not the answer, using a library that has the functionality is.Softwood
Official doc: How to migrate from Newtonsoft.Json to System.Text.Json - Required propertiesChyou
W
11

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.

Wo answered 12/11, 2022 at 3:37 Comment(3)
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.Sabaean
@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.Wo
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!Wo
J
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
Jame answered 18/10, 2019 at 4:6 Comment(0)
W
11

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.

Wo answered 12/11, 2022 at 3:37 Comment(3)
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.Sabaean
@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.Wo
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!Wo
S
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.

Sabaean answered 11/3, 2021 at 2:16 Comment(4)
How exactly does NotNull help? It adds a static code analysis check, correct?Fp
@Fp 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.Sabaean
Interesting. I didn't find this information in the Microsoft documentation. Thanks for your explanation.Fp
Note: For WebAPI you also get 500 errors (with a stack trace) instead of 400 bad requestRhodos
A
8

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);
Abrade answered 11/12, 2019 at 20:51 Comment(0)
K
2

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
Kantos answered 11/11, 2022 at 17:14 Comment(0)
S
-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.

Swick answered 15/1, 2020 at 11:37 Comment(4)
Where does it help?Propylite
For example when you want to annotate that a certain property is required in a request object, passed to an API controller.Swick
That attribute may be used up the stack in specific frameworks (like MVC), but isn't enforced by System.Text.Json's JsonSerializer.Albuminous
The initial question was probably more about enforcing required properties (as it is done in Newtonsoft.Json and not in System.Text.Json) than just mark them as required...Propylite

© 2022 - 2024 — McMap. All rights reserved.