У меня есть класс 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] становится типом ресурса.
Я думаю, что вам подходит проблема @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 позволяет мне безболезненно сериализовать этот ресурс. Я собираюсь реализовать это сегодня, и я приму ваш ответ, если он будет работать как задумано.