Newtonsoft.Json
Newtonsoft.Json copied to clipboard
JsonConverter for enums not working on records
Update (Apr 08)
I've just realized that this has more to do with how records synthesize the code than some unexpected behavior with Json.net. I was under the (wrong) impression that when declaring records properties with positional parameters, I was annotating the actual property, but that's not the case - what I was annotating in the sample below was the constructor parameter, which, of course, isn't used by Json.net.
Although I still would like that somehow annotating those properties would lead to the behavior I was expecting, I'm no longer sure if that should be the case. First, because I'm not sure if there's metadata connecting the property to the constructor parameter, so it might be impossible to determine which property should get which attribute; also, because maybe that's really not the desired behavior - after all, we are annotating the wrong thing.
In any case, I would love a solution for this use case: I want to use records for DTO's, mostly because of their simplified syntax - I usually don't care for the equalities overrides and whatnot, just the concise syntax for declaring an anemic class, so, if I actually need to write up the properties as in a class I lose the conciseness advantage.
Original Post
Source/destination types
public enum Gender
{
Male,
Female
}
public record MyRecord([JsonConverter(typeof(StringEnumConverter))] Gender Gender);
Expected behavior
Since Gender has a JsonConverter
attribute, I expected that upon serialization I would get the string name of the enum value, like:
{"Gender":"Male"}
Actual behavior
For some reason, when the object is of record type, JsonConverter is ignored:
{"Gender":0}
Steps to reproduce
Take a look at a repo that I created to reproduce the problem:
https://github.com/bruno-brant/NewtonsoftRecordIssues
Semi-related feature request: https://github.com/JamesNK/Newtonsoft.Json/issues/2039
PSA: the current workaround is to annotate the enum declaration - assuming that you can access the enum (which you probably can workaround by just creating a mimicking type, which is easy enough).
Another workaround I've found is to specify that the generated property is the target of the attribute:
public record MyRecord([property: JsonConverter(typeof(StringEnumConverter))] Gender Gender);
@rehret Thank you! I didn't know about this syntax. I decided to use records
without actually researching enough about them; I was really wondering if the compiler team would have forgotten about this simple use case!
Now, while I'm tempted to close the issue with your solution, I'm here thinking (and would love maintainers feedback on this): I realized after I wrote the update in the issue that, of course, JSON.NET is using the constructor to initialize the object (as it should since the record above does not define a parameterless constructor). Therefore, it's mapping JSON properties to constructor parameters (which I never knew it did automatically), but it is not considering the JsonConverter
that annotates the parameter).
So I keep thinking that this would be the expected behavior, to have JSON.NET respect annotations on the constructor.
However, we must remember that this would solve deserialization only. Serialization still uses the properties and therefore the JsonConverter on the constructor would be ignored (as it should).
@rehret , AFAIK, System.Text.Json
works with attributes applied to constructor params of the records. Would be nice to have same behavior from Newton serializer.