FasterXML / jackson-databind

General data-binding package for Jackson (2.x): works on streaming API (core) implementation(s)
Apache License 2.0
3.51k stars 1.37k forks source link

Default value for third-party enums when deserializing (maybe use `@JsonProperty(defaultValue = "DEFAULT_ENUM")`) #3756

Open ZeroOne3010 opened 1 year ago

ZeroOne3010 commented 1 year ago

Is your feature request related to a problem? Please describe.

Let's say I've got a server running an API, and there's a DTO class like this using the nv-i18n library (could be any external library that provides an enum but anyway):

import com.neovisionaries.i18n.CountryCode;

public class Example {
    public CountryCode country;
    public String message;
}

Now, everything is fine when the clients are nice and call it with sensible country codes, but when someone calls it with "XX" Jackson breaks because XX is not a valid enum value and cannot be deserialized. The enum would have a sensible default for these cases, UNKNOWN, but as it is a third-party library, I cannot simply go and insert the @JsonEnumDefaultValue annotation to the CountryCode enum.

Describe the solution you'd like / Usage example

I'd like to annotate the enum field in my class so that the annotation would describe, which value to use by default in case serialization would fail. Something like this:

    @DeserializationDefaultEnumValue(CountryCode.UNKNOWN)
    public CountryCode country;
cowtowncoder commented 1 year ago

Ok, first of all: yes, I can see how this can be problematic.

@ZeroOne3010 Have you consider use of Mix-in Annotations? They can be associated externally to 3rd party classes and work as if annotation was in target class. There are some examples available, f.ex:

https://springframework.guru/jackson-mix-in-annotation/

although I must admit I have never it with @JsonEnumDefaultValue -- there may be problems in processing of Enum types wrt mix-ins.

As to other solutions, unfortunately I do not think annotation you suggest is possible to implement, due to limited flexibility of Java annotations. It is not (I think) possible to specify value type of "Any Enum" for annotation properties: only strongly-typed specific Enum type is allowed. And that would not be possible for general-purpose annotation.

cowtowncoder commented 1 year ago

Also: I assume you have considered enabling DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL. But just in case if not, that might work around the problem (maybe with some post-processing).

ZeroOne3010 commented 1 year ago

Hey, thanks for the quick reply and the tips! I admit I completely missed the READ_UNKNOWN_ENUM_VALUES_AS_NULL possibility, I think that should work just fine in my case!

Also, yes, now that I think of it, you're most likely right in that annotations cannot be used like I suggested. The JUnit project, for one, takes enum values as Strings in its annotations (in e.g. @EnumSource). I just didn't make the connection here. Would you consider something like @DeserializationDefaultEnumValue("UNKNOWN") then? I know it's a somewhat annoying format... But at least IntelliJ Idea actually has a custom inspector for JUnit which highlights the String value if it doesn't match any actual value of the given enum, which slightly eases the pain. I believe something like that could be created for Jackson too, eventually. However, if you think the mix-ins would be the way to go in this case and there's no need for a custom annotation, I won't argue with you. :) Just wanted to start a discussion, as I didn't really find any from before.

cowtowncoder commented 1 year ago

I am open to new annotations, although one concern here is the specificity of annotation. But it would probably be possible to augment @JsonEnumDefaultValue to allow name parameter or something to reuse that one. Although I guess might be confusing.

Actually... there is already

@JsonProperty(defaultValue = "...")

which could perhaps work as an alternative. And it is actually already accessible via JacksonAnnotationIntrospector so that's... good. It might even be wired with BeanProperty metadata (I'd have to look if it is). If so that'd be good start.

But beyond figuring out user-facing API there's the implementation which would not be trivial. I think using createContextual (by EnumDeserializer, and one for EnumSet) could work here.

So yeah I think that is doable. I doubt I'll have time to directly work on this, but as usual would make time to help others, reviewing code and suggesting approaches (like above).