Прочитайте массив JSON как перечисления с типом, основанным на свойстве

Мой ввод 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);
}

Я также знаю, как создать для этого собственный десериализатор, но это делает код гораздо более хрупким, особенно если он будет расширен или изменен в будущем.

ВСЕ ли элементы массива являются перечислениями? V2 Score кажется другим, и вы не указываете его рядом с Age. Если да, то я не думаю, что вы сможете добиться этого декларативно с помощью @JsonTypeInfo и т. д., но вам нужно будет написать свой собственный сериализатор/десериализатор, тогда это будет довольно просто.

AndrewL 25.06.2024 01:48

Можете ли вы привести пример ожидаемого результата?

dani-vta 25.06.2024 05:23

Добавлена ​​дополнительная информация по вашим вопросам

EarthTurtle 25.06.2024 19:46

не добавляйте разделы «редактирования» в свой вопрос — они предназначены для ответов, которые устарели и требуют отдельного раздела редактирования, чтобы уточнить, что изменилось с момента публикации. Просто добавьте недостающие детали в свое сообщение, чтобы любой, кто найдет ваш вопрос, читал его так же, как любой другой.

Mike 'Pomax' Kamermans 25.06.2024 19:49

Возможно, это я, но этот вопрос пока совершенно неясен. Я не понимаю, как элемент с полями id, name, type и value должен быть десериализован в POJO Context с совершенно разными полями. Вам нужно преобразовать весь массив JSON в один Context или массив Context? Класс Context даже не полностью соответствует тому «примеру» с вызовами геттеров. Более того, вы продолжаете называть v2Score и threat_sources «элементами», а это значения. Вы имеете в виду, что вам не нужны некоторые поля, если они имеют определенные значения?

dani-vta 25.06.2024 19:59

еще раз переработал вопрос с более подробными примерами, надеюсь, на этот раз все понятно.

EarthTurtle 25.06.2024 22:59

@EarthTurtle спасибо за расширение вашего вопроса и добавление фрагмента с JsonTypeInfo. На мой взгляд, последняя часть была весьма важна для вопроса. Однако я считаю, что то, чего вы хотите достичь, невозможно сделать с помощью одних аннотаций, поскольку вы пытаетесь преобразовать массив значений в объект с некоторыми из этих полей, отображающих определенные значения перечисления. Кажется, это настолько конкретный случай, что я не думаю, что комбинация аннотаций могла бы вам в этом помочь (надеюсь, я ошибаюсь).

dani-vta 27.06.2024 01:59

@dani-vta Я ценю это. Сделав последний осмотр, оказалось, что то, о чем я прошу, может оказаться невозможным с помощью java-перечислений github.com/FasterXML/jackson-databind/issues/2739

EarthTurtle 27.06.2024 18:50

Похоже, приведенная выше ссылка подтверждает, что этот вопрос не имеет решения. Приемлемо ли написать и принять мой собственный ответ, в котором будет указано то же самое? Просто чтобы кто-нибудь в будущем понял, что это тупик.

EarthTurtle 27.06.2024 19:04

@EarthTurtle да, это так.

dani-vta 27.06.2024 20:14
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
10
75
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я подтвердил, что то, что я пытаюсь сделать, невозможно, поскольку Java не предоставляет средств для десериализации в «любое общее перечисление» https://github.com/FasterXML/jackson-databind/issues/2739

Другие вопросы по теме