My JSON input is this:
{
// other fields
"context": [
{
"id": "age",
"name": "Age",
"type": "string",
"value": "730 days +"
},
{
"id": "v2Score",
"name": "V2 Score",
"type": "number",
"value": 5.9
},
{
"id": "maturity",
"name": "Maturity",
"type": "string",
"value": "High"
},
{
"id": "coverage",
"name": "Product Coverage",
"type": "string",
"value": "Low"
},
{
"id": "threat_intensity",
"name": "Threat Intensity",
"type": "string",
"value": "Very Low"
},
{
"id": "threat_recency",
"name": "Threat Recency",
"type": "string",
"value": "No recorded events"
},
{
"id": "threat_sources",
"name": "Threat Sources",
"type": "string",
"value": "No recorded events"
}
]
}
and my Context
POJO class looks like this
class Context {
private final Age age;
private final Maturity maturity;
private final Coverage coverage;
private final ThreatIntensity threatIntensity;
private final ThreatRecency threatRecency;
// constructor, getters
}
All of the above fields in Context
are enum
s. Using Age
as an example:
enum Age {
@JsonProperty("0 - 7 days")
LESS_THAN_ONE_WEEK,
@JsonProperty("7 - 30 days")
ONE_WEEK_TO_ONE_MONTH,
@JsonProperty("30 - 365 days")
ONE_MONTH_TO_ONE_YEAR,
@JsonProperty("365 - 730 days")
ONE_TO_TWO_YEARS,
@JsonProperty("730 days + ")
MORE_THAN_TWO_YEARS
}
the object in the array with "id": "age"
corresponds to the Age
enum type, and "value": "730 days +"
corresponds to Age.MORE_THAN_TWO_YEARS
.
How can I deserialize this array of objects into individual enums? Each object in the array corresponds to exactly one type of enum (i.e., there will never be two objects in the array with the same id
), but the objects in the array are not guaranteed to be in the same order every time. Is there a way to do this with annotations, such as @JsonTypeInfo
? Additionally, while I don't expect any of the enums to be missing, I'd like to be able to pass "missing" enums as null
. For example, if the object with "id": "maturity"
was not present in the array, the associated Maturity
enum object should be deserialized as null
. So far data returned from this API has either included all or none of these elements (resulting in "context": []
), although I have no guarantee that's true everywhere.
I can also ignore any objects in the array with an id
that I don't care about (in this case, v2Score
and threat_sources
). I included them in the example to demonstrate that there may be data outside of what I want that should be ignored. Additionally, the "name"
and "type"
fields in each of the objects can be ignored, as only the id
is needed to determine the enum type, and only "value"
is needed to select which enum of that type.
I got about as far as this before getting stuck on what to do. I'm doing my best to avoid making switch
statements over the id
or value
fields, as additions in the future would likely cause bugs.
@JsonCreator
Context(
@JsonProperty("age") Age age,
@JsonProperty("Maturity") Maturity maturity,
@JsonProperty("coverage") Coverage coverage,
@JsonProperty("threat_intensity") ThreatIntensity intensity,
@JsonProperty("threat_recency") ThreatRecency recency,
) {
// basic constructor
}
For the above input, the following object should be created:
context.getAge() // Age.MORE_THAN_TWO_YEARS
context.getMaturity() // Maturity.HIGH
context.getCoverage() // Coverage.LOW
context.getThreatIntensity() // ThreatIntensity.VERY_LOW
context.getThreatRecency() // ThreatRecency.NONE
Not all of the elements in the input array are (logically) enums, but all of the elements that I care about are enums.
I'm guessing I need to use @JsonTypeInfo
on each of the constructor arguments in some way? From what I know of how it works, if I was able to annotate Enum
like this, it would deserialize the individual enums correctly. I'm not sure how to translate the array into individual parameters though.
@JsonTypeInfo(
use = JsonTypeInfo.Id.CUSTOM,
as = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "id",
defaultImpl = Void.class,
visible = false)
@JsonSubTypes({
@Type(value=Age.class, name="age"),
@Type(value=Maturity.class, name="maturity")
@Type(value=Coverage.class, name="coverage")
@Type(value=ThreatIntensity.class, name="threat_intensity")
@Type(value=ThreatRecency.class, name="threat_recency")})
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
// annotating the implicitly-declared valueOf(String) method for Enums
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
@JsonIgnoreProperties(ignoreUnknown = true)
public static abstract <T extends Enum<T>> T valueOf(@JsonProperty("value") String name);
}
I do also know how to make a custom deserializer for this, but doing so makes the code much more brittle, especially if it's extended or changed in the future.
I've confirmed that what I am attempting is not possible, as java does not provide the means to deserialize to "any generic enum" https://github.com/FasterXML/jackson-databind/issues/2739