-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: Extend JsonIgnoreCondition
#66490
Comments
Tagging subscribers to this area: @dotnet/area-system-text-json Issue DetailsBackground and motivationSometimes we may want to ignore some properties only when serialize, included when deserialize, currently, we had public enum JsonIgnoreCondition
{
Never,
Always,
WhenWritingDefault,
WhenWritingNull,
} Maybe we could add new conditions like API Proposalnamespace System.Text.Json.Serialization;
public enum JsonIgnoreCondition
{
Never,
Always,
WhenWritingDefault,
WhenWritingNull,
WhenWriting,
WhenReading
} API Usagepublic class TestModel
{
public int Age { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWriting)]
public string Name { get; set; }
}
var json = JsonSerializer.Serialize(new TestModel{ Age = 10, Name="Mike" });
Assert.Equal("{\"Age\":10}", json);
json = "{\"Age\":10, \"Name\":\"Mike\"}";
var model = JsonSerializer.Deserialize<TestModel>(json);
Assert.Equal("Mike", model.Name); Alternative DesignsNo response RisksNo response
|
JsonIgnore
JsonIgnoreCondition
Related to #55781. In general we want to be careful about adding more states in the |
Tagging @layomia for a second opinion. If this seems reasonable, I think I can mark it ready for review. |
There should be public class TestClass
{
public string FirstName { get; set; } = string.Empty;
public string[] List { get; set; } = Array.Empty<string>();
public TestClass(){}
} And deserialization var testClassJson = "{\"FirstName\":null,\"List\":null}";
TestClass testclass1 = JsonSerializer.Deserialize<TestClass>(testClassJson, new JsonSerializerOptions()
{
IgnoreNullValues = true
}); This will produce:
Meanwhile you can't express such behavior with FYI: the Newtonsoft.Json is able to ignore null values on reading with Also, the issue #62086 shows that I'm not alone thinking the same way that there should be attribute for null deserialization and how misleading the documentation / obsolete attribute on |
This is a tremendous problem and I JUST ran into this during an upgrade. The fact that this wasn't thought through as a potential migration path and called out in the documentation is incredibly frustrating, especially since as @ScarletKuro pointed out, this behavior previously worked in It would be so greatly appreciated if there was a little more foresight around deprecating functionality coupled with an appreciation for how that functionality is being used, and a little less opinion on what you think is the appropriate usage, without regards to how it worked previously. |
@layomia said to just suppress the warring and use Update: Also, what I found out, is that |
@ScarletKuro with contract customization you can simply remove Get/Set from property to prevent serialization or deserialization |
@krwq would it be possible to write a small snippet here illustrating how this can be done? |
As an extension, null should be treated the same way
what would be nice is if 'null' values were treated as 'no value' and let the defaults applied
Today the above will throw 'Cannot get the value of a token type 'Null' as a number.' I can write a converter for every type to do this easily today - but think this should be an option. (I would need this boiler plate code for boolean, int, short, int64, decimal, double, etc...)
|
Here's an example of how the feature can be implemented using .NET 7 contract customization: [Flags]
public enum MyIgnoreCondition
{
WhenWriting = 1,
WhenReading = 2,
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public sealed class MyIgnoreConditionAttribute : Attribute
{
public MyIgnoreCondition IgnoreCondition { get; }
public MyIgnoreConditionAttribute(MyIgnoreCondition ignoreCondition) => IgnoreCondition = ignoreCondition;
}
public class MyIgnoreConditionResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo jti = base.GetTypeInfo(type, options);
foreach (JsonPropertyInfo jsonPropertyInfo in jti.Properties)
{
if (jsonPropertyInfo.AttributeProvider?.GetCustomAttributes(typeof(MyIgnoreConditionAttribute), inherit: false) is [MyIgnoreConditionAttribute attr, ..])
{
MyIgnoreCondition ignoreCondition = attr.IgnoreCondition;
if ((ignoreCondition & MyIgnoreCondition.WhenWriting) != 0)
{
jsonPropertyInfo.Get = null;
}
if ((ignoreCondition & MyIgnoreCondition.WhenReading) != 0)
{
jsonPropertyInfo.Set = null;
}
}
}
return jti;
}
} which can be used as follows: var options = new JsonSerializerOptions { TypeInfoResolver = new MyIgnoreConditionResolver() };
MyPoco value = new MyPoco { IgnoreOnWrite = 1, IgnoreOnRead = 2 };
Console.WriteLine(JsonSerializer.Serialize(value, options)); // {"IgnoreOnRead":2}
string json = """{"IgnoreOnWrite":1,"IgnoreOnRead":2}""";
value = JsonSerializer.Deserialize<MyPoco>(json, options);
Console.WriteLine(value.IgnoreOnRead); // 0
public class MyPoco
{
[MyIgnoreCondition(MyIgnoreCondition.WhenWriting)]
public int IgnoreOnWrite { get; set; }
[MyIgnoreCondition(MyIgnoreCondition.WhenReading)]
public int IgnoreOnRead { get; set; }
} |
How do you combine it with the source generator when you have your own jsonSerializerOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine(new MyIgnoreConditionResolver(), MySerializerContext.Default); But looks like |
The var ignoreConditionResolver = new MyIgnoreConditionResolver(MySerializerContext.Default);
public class MyIgnoreConditionResolver : IJsonTypeInfoResolver
{
private readonly IJsonTypeInfoResolver _source;
public class MyIgnoreConditionResolver(IJsonTypeInfoResolver source) => _source = source;
public override JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo? jti = _source.GetTypeInfo(type, options);
if (jti is null)
return null;
foreach (JsonPropertyInfo jsonPropertyInfo in jti.Properties)
{
if (jsonPropertyInfo.AttributeProvider?.GetCustomAttributes(typeof(MyIgnoreConditionAttribute), inherit: false) is [MyIgnoreConditionAttribute attr, ..])
{
MyIgnoreCondition ignoreCondition = attr.IgnoreCondition;
if ((ignoreCondition & MyIgnoreCondition.WhenWriting) != 0)
{
jsonPropertyInfo.Get = null;
}
if ((ignoreCondition & MyIgnoreCondition.WhenReading) != 0)
{
jsonPropertyInfo.Set = null;
}
}
}
return jti;
}
} |
Thanks, the source generator starts to work. However, there is another problem now. JsonTypeInfo? jti = _source.GetTypeInfo(type, options); the |
Ah yes. Unfortunately |
If the custom attributes do not work together with the source generator and contract customization, then this proposal is valid. upd: or maybe in the future it would be possible to make the source generator to write somewhere the list of custom attributes and could be added to the "contract customization improvements" plan.
I think I was wrong, because apparently it is respected by the source generator. Only concerned is that this can be removed in future .NET since its marked as obsolete. |
Generally speaking, it should be possible to fine tune ignore settings via the If you're willing to go the extra mile, it should be possible to recover the |
Should we create a separate issue for this? In JavaScript, NULL is same as 0 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_coercion. JSON is JavaScript. I would expect, at least behind an option, that value types should treat NULL as 'default' when deserializing. |
@bartonjs thanks for posting the design review. Very helpful. I think there is some misunderstanding about common use cases in the thread above. The ask was not to ignore on read always, it was to ignore on read when null, which this design proposal absolutely does not handle. The need is to replace behavior guaranteed by To make things worse, you also didn't provide a solution in the design review to use combinatorial conditions. What we really need is |
In our case, we have some properties which expected not to be serialized, we're using the Added the |
The proposal has already been approved with the two new additions, it would be impractical to re-review it just because the scope got extended after the fact. I'm also not sure what the purpose of I've edited your OP to remove the |
Yes, this is the main use case of it. It's useful when you deal with 3rd party json, which may send sets as null values for list / array when you want it to be empty collection instead of null (as example). If we search in the repository, there is a community interest in having this feature
This, unfortunately, doesn't currently work with one of the main aspects - source generation, if you want to make it attribute based like in the Newtonsoft.Json |
Please open a separate issue. API review is complete and we can't change the scope after the fact. |
This is not entirely accurate. You can in fact run contract customization against source gen, and even query for custom attributes: it's the type of reflection that works even in Native AOT applications. |
It appears that a new issue was created(#90007) and the problem is similar to the one we discussed here. Based on my understanding, implementing the @WeihanLi do you have interest in creating a new API proposal, or I should do it? |
@ScarletKuro add a new proposal here #90011 |
Let's use #83706 to track |
@eiriktsarpalis I've already implemented in #69574 |
Hello @onurkanbakirci are you still working on this? I'd like to try to take this if you're not going to implement this |
@WeihanLi feel free to open a PR. |
Moving to 10.0.0 since feature development for .NET 9 is now completed. |
Background and motivation
Sometimes we may want to ignore some properties only when serialize, included when deserialize, currently, we had
JsonIgnoreCondition
Maybe we could add new conditions like
WhenWriting
/WhenReading
(orWhenSerializing
/WhenDeserializing
...)API Proposal
API Usage
Alternative Designs
No response
Risks
No response
The text was updated successfully, but these errors were encountered: