Newtonsoft.Json icon indicating copy to clipboard operation
Newtonsoft.Json copied to clipboard

JsonConverter for enums not working on records

Open bruno-brant opened this issue 3 years ago • 5 comments

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

bruno-brant avatar Apr 05 '21 17:04 bruno-brant

Semi-related feature request: https://github.com/JamesNK/Newtonsoft.Json/issues/2039

bartelink avatar Apr 07 '21 01:04 bartelink

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).

bruno-brant avatar Apr 07 '21 21:04 bruno-brant

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 avatar Apr 09 '21 14:04 rehret

@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).

bruno-brant avatar Apr 09 '21 14:04 bruno-brant

@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.

voroninp avatar May 16 '22 10:05 voroninp