Мой ввод JSON таков:
{
// 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"
}
]
}
и мой класс Context
POJO выглядит так
class Context {
private final Age age;
private final Maturity maturity;
private final Coverage coverage;
private final ThreatIntensity threatIntensity;
private final ThreatRecency threatRecency;
// constructor, getters
}
Все вышеперечисленные поля в Context
являются enum
. Используя Age
в качестве примера:
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
}
объект в массиве с "id": "age"
соответствует типу перечисления Age
, а "value": "730 days +"
соответствует Age.MORE_THAN_TWO_YEARS
.
Как я могу десериализовать этот массив объектов в отдельные перечисления? Каждый объект в массиве соответствует ровно одному типу перечисления (т. е. в массиве никогда не будет двух объектов с одинаковым id
), но не гарантируется, что объекты в массиве каждый раз будут находиться в одном и том же порядке. Есть ли способ сделать это с помощью аннотаций, например @JsonTypeInfo
? Кроме того, хотя я не ожидаю, что какие-либо перечисления будут отсутствовать, мне бы хотелось иметь возможность передавать «отсутствующие» перечисления как null
. Например, если объект с "id": "maturity"
не присутствовал в массиве, связанный объект перечисления Maturity
должен быть десериализован как null
. До сих пор данные, возвращаемые из этого API, либо включали все эти элементы, либо не включали ни одного из этих элементов (что приводит к "context": []
), хотя я не гарантирую, что это верно везде.
Я также могу игнорировать любые объекты в массиве с id
, которые меня не волнуют (в данном случае v2Score
и threat_sources
). Я включил их в пример, чтобы продемонстрировать, что могут быть данные, выходящие за рамки того, что мне нужно, и которые следует игнорировать. Кроме того, поля "name"
и "type"
в каждом из объектов можно игнорировать, поскольку для определения типа перечисления требуется только id
, и только "value"
необходимо для выбора перечисления этого типа.
Я дошел до этого, прежде чем застрял в том, что делать. Я делаю все возможное, чтобы не делать операторов switch
над полями id
или value
, так как дополнения в будущем могут привести к ошибкам.
@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
}
Для приведенного выше ввода необходимо создать следующий объект:
context.getAge() // Age.MORE_THAN_TWO_YEARS
context.getMaturity() // Maturity.HIGH
context.getCoverage() // Coverage.LOW
context.getThreatIntensity() // ThreatIntensity.VERY_LOW
context.getThreatRecency() // ThreatRecency.NONE
Не все элементы входного массива являются (логическими) перечислениями, но все элементы, которые меня интересуют, являются перечислениями.
Я предполагаю, что мне нужно каким-то образом использовать @JsonTypeInfo
для каждого аргумента конструктора? Насколько я знаю, как это работает, если бы я мог аннотировать Enum
вот так, отдельные перечисления были бы правильно десериализованы. Однако я не уверен, как перевести массив в отдельные параметры.
@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);
}
Я также знаю, как создать для этого собственный десериализатор, но это делает код гораздо более хрупким, особенно если он будет расширен или изменен в будущем.
Можете ли вы привести пример ожидаемого результата?
Добавлена дополнительная информация по вашим вопросам
не добавляйте разделы «редактирования» в свой вопрос — они предназначены для ответов, которые устарели и требуют отдельного раздела редактирования, чтобы уточнить, что изменилось с момента публикации. Просто добавьте недостающие детали в свое сообщение, чтобы любой, кто найдет ваш вопрос, читал его так же, как любой другой.
Возможно, это я, но этот вопрос пока совершенно неясен. Я не понимаю, как элемент с полями id
, name
, type
и value
должен быть десериализован в POJO Context
с совершенно разными полями. Вам нужно преобразовать весь массив JSON в один Context
или массив Context
? Класс Context
даже не полностью соответствует тому «примеру» с вызовами геттеров. Более того, вы продолжаете называть v2Score
и threat_sources
«элементами», а это значения. Вы имеете в виду, что вам не нужны некоторые поля, если они имеют определенные значения?
еще раз переработал вопрос с более подробными примерами, надеюсь, на этот раз все понятно.
@EarthTurtle спасибо за расширение вашего вопроса и добавление фрагмента с JsonTypeInfo. На мой взгляд, последняя часть была весьма важна для вопроса. Однако я считаю, что то, чего вы хотите достичь, невозможно сделать с помощью одних аннотаций, поскольку вы пытаетесь преобразовать массив значений в объект с некоторыми из этих полей, отображающих определенные значения перечисления. Кажется, это настолько конкретный случай, что я не думаю, что комбинация аннотаций могла бы вам в этом помочь (надеюсь, я ошибаюсь).
@dani-vta Я ценю это. Сделав последний осмотр, оказалось, что то, о чем я прошу, может оказаться невозможным с помощью java-перечислений github.com/FasterXML/jackson-databind/issues/2739
Похоже, приведенная выше ссылка подтверждает, что этот вопрос не имеет решения. Приемлемо ли написать и принять мой собственный ответ, в котором будет указано то же самое? Просто чтобы кто-нибудь в будущем понял, что это тупик.
@EarthTurtle да, это так.
Я подтвердил, что то, что я пытаюсь сделать, невозможно, поскольку Java не предоставляет средств для десериализации в «любое общее перечисление» https://github.com/FasterXML/jackson-databind/issues/2739
ВСЕ ли элементы массива являются перечислениями?
V2 Score
кажется другим, и вы не указываете его рядом сAge
. Если да, то я не думаю, что вы сможете добиться этого декларативно с помощью@JsonTypeInfo
и т. д., но вам нужно будет написать свой собственный сериализатор/десериализатор, тогда это будет довольно просто.