Сопоставление одного пользовательского поля Java со многими полями JSON с помощью Jackson @JsonDeserializer

У меня есть класс java, представляющий JSON с использованием Jackson. Все поля, за одним исключением, можно перевести вообще без аннотаций. 1-к-1, простые переводы (хотя некоторые из них являются вложенными POJO).

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyPojo {
    private String someString;
    private AnotherPojo someOtherPojo;
    //The problem child:
    private Object value;
}

Поле value, которое является исключением из этого правила, может представлять собой любое поле JSON, соответствующее value*, где * — подстановочный знак неопределенной длины. Это означает, что valueString или valueReference в JSON будут присвоены этому полю с утверждением, что может присутствовать только один.

{
  "someString": "asdasdadasdsa",
  "someOtherPojo": {
    "someOtherProperty": "whatever"
  },
  "valueCodeableConcept": {
    "text": "text value",
    "coding": [
      {
        "code": "my-code"
      }
    ]
  }
}

Используя пользовательский десериализатор в классе верхнего уровня, я могу очистить все поля из корневого узла (baseNode в следующем примере), которые начинаются с value, и соответствующим образом установить поле значения. Это прекрасно работает! Однако при этом мне теперь нужно вручную установить все остальные поля в этом классе MyPojo в моем десериализаторе, и я должен поместить пользовательскую копию этого десериализатора в каждый POJO, который использует такое поле, как value*.

private Object parseValueX(JsonNode baseNode, DeserializationContext context) throws IOException {
    //Find the concrete implementation referred to by the value[x] field
    Set<String> concreteNames = new HashSet<>();
    baseNode.fieldNames().forEachRemaining(name -> {
        if (name.startsWith("value")) {
            concreteNames.add(name);
        }});

    if (concreteNames.isEmpty()) {
        return null;
    }

    if (concreteNames.size() > 1) {
        throw JsonMappingException.from(context, "The field value[x] must have no more than one concrete " +
                "implementation, ex: valueCode, valueCodeableConcept, valueReference");
    }

    String concreteName = concreteNames.stream().findFirst().orElseThrow(() -> new RuntimeException(""));
    JsonNode jsonSource = baseNode.get(concreteName);

    //...deserialize from jsonSource, solved, but not relevant to question...
}

Чтобы применить это к любому свойству value* в любом POJO, я попытался переместить десериализатор в атрибут value в POJO (тогда как сейчас он находится на ресурсе верхнего уровня). Первый недостаток заключается в том, что десериализатор даже не вызывается, пока свойство JSON точно не соответствует value. На самом деле мне нужно, чтобы весь родительский ресурс JSON был передан этому десериализатору для конкретного поля, чтобы я мог найти соответствующее поле и назначить его -- ИЛИ -- мне нужно иметь возможность включить десериализатор MyPojoТолько assign одно поле value и позволить автоматической десериализации позаботиться об остальных. Как мне сделать что-либо из этого?


Для тех, кто интересуется моей мотивацией, я реализую спецификацию HL7 FHIR, которая определяет общие атрибуты, называемые value[x] (вот один пример: https://www.hl7.org/fhir/extensibility.html#Расширение), где [x] становится типом ресурса.

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

Ответы 1

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

Я думаю, что вам подходит проблема @JsonAnySetter. Эта аннотация метода говорит Джексону направлять неизвестные свойства к нему. аргумент (в вашем случае) представляет собой карту, содержащую json-дерево неизвестного свойства. если я правильно понимаю ваш код, имя свойства value содержит имя класса целевого Pojo. поэтому, когда у вас есть имя класса, вы можете указать Джексону, как «десериализовать» карту в экземпляр целевого класса.

Вот пример, основанный на коде из вопроса

public class MyPojo {
    public String someString;  // made properties into public for this example...
    public AnotherPojo someOtherPojo;
    public Object value;

    @JsonAnySetter
    public void setValue(String name, Object value) {
        System.out.println(name + " " + value.getClass());
        System.out.println(value);

        // basic validation
        if (name.startsWith("value") && value instanceof Map) {
            String className = "com.company." + name.substring("value".length());
            System.out.println(name + " " + value.getClass() + " " + className);
            System.out.println(value);
            try {
                // nice of Jackson to be able to deserialize Map into Pojo :)
                ObjectMapper mapper = new ObjectMapper();
                this.value = mapper.convertValue(value, Class.forName(className));
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(this.value + " " + this.value.getClass());
        }

    }
}

public class AnotherPojo {
    public String someOtherProperty;
}

public class CodeableConcept {
    public String text;
    public Code[] coding;
}

public class Code {
    public String code;
}

Это фантастика, и противоположная аннотация @JsonAnyGetter позволяет мне безболезненно сериализовать этот ресурс. Я собираюсь реализовать это сегодня, и я приму ваш ответ, если он будет работать как задумано.

Bit Fracture 08.04.2019 19:07

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