У меня есть следующий класс
public class Customer<T>{
private String name;
private String store;
private T details;
}
public class Details{
private String dob;
private boolean validated;
}
Десериализация работает, когда JSON имеет следующий вид
{
"name": "John Smith",
"store": "Walmart",
"details": {
"dob": "1900/01/01",
"validated": true
}
}
Но когда данный ответ JSON похож на приведенный ниже, он терпит неудачу.
{
"name": "John Smith",
"store": "Walmart",
"details": [
"Failed customer does not exist in the system", "Please contact administrator"
]
}
Есть ли способ с помощью картографа Джексона справиться с этими сценариями? Если details предоставляется как POJO, десериализуйте его как таковой, в противном случае десериализуйте details как String. Я определил свой картограф следующим образом:
private static final ObjectMapper Mapper = new ObjectMapper() {{
setSerializationInclusion(JsonInclude.Include.NON_NULL);
setDateFormat(new SimpleDateFormat("MM/dd/yyyy HH:mm:ss z", Locale.US));
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}};
Кроме того, каков ожидаемый результат, когда details появляется в виде массива строк в json, объединяя строки в одну и присваивая ее свойству dob объекта Details?
Я использую общий тип T для проверки в будущем, если вместо Details получу другой объект. Моим ожидаемым результатом для получения подробной информации будет сохранение массива json в виде одной строки в поле.




Если до десериализации вы уже знали, что содержит json для поля details, вы могли бы просто прочитать общий Customer с помощью TypeReference. Однако в этом сценарии сообщения об ошибках будут иметь вид List<String> вместо String, больше придерживаясь структуры json.
//Reading a generic Customer with a Details pojo
Customer<Details> c1 = mapper.readValue(jsonPojo, new TypeReference<Customer<Details>>() {});
//Reading a generic Customer with an array of String error messages
Customer<List<String>> c2 = mapper.readValue(jsonArray, new TypeReference<Customer<List<String>>>() {});
Однако это решение требует заранее знать, что содержит json (чего не может быть), или заранее проанализировать его, а затем приступить к соответствующей десериализации (что может быть утомительным).
Итак, чтобы предложить более общее решение, сохраняя при этом гибкость дженериков, необходимо использовать собственный десериализатор для поля details, поскольку логика весьма специфична и конфигурации только на ObjectMapper этого не сделают.
Если сведения указаны как pojo, то запишите их как таковые, в противном случае запишите сведения как строковое значение.
Однако определение пользовательского десериализатора для универсального типа T не имеет особого смысла, поскольку после реализации логики десериализации для двух случаев Details и String мы будем вынуждены привести результат к универсальному типу T, что сводит на нет весь смысл использования дженериков. и теряют свое главное преимущество безопасности типов во время компиляции.
Вместо этого мы могли бы найти общий знаменатель типов среди возможных типов данных для поля details. На данный момент details может быть либо экземпляром Details, либо String. Класс String реализует интерфейс Serializable, поэтому, если бы класс Details тоже реализовал его, мы могли бы использовать Serializable в качестве верхней границы для универсального типа T и определить собственный десериализатор для Serializable. Этот подход подразумевает, что каждый тип, передаваемый вместо T, должен быть подтипом Serializable. Это не очень строгое требование, поскольку большинство классов Java уже реализуют интерфейс, в то время как наши классы также могут легко реализовать его без необходимости предоставления какой-либо реализации.
public class Customer<T extends Serializable> {
private String name;
private String store;
@JsonDeserialize(using = MyDeserializer.class)
private T details;
// ... implementation ...
}
public class Details implements Serializable {
private String dob;
private boolean validated;
// ... implementation ...
}
public class MyDeserializer extends StdDeserializer<Serializable> {
public MyDeserializer() {
this(null);
}
public MyDeserializer(Class<?> c) {
super(c);
}
@Override
public Serializable deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
JsonNode node = p.getCodec().readTree(p);
if (node.isArray()) {
return StreamSupport.<JsonNode>stream(Spliterators.spliteratorUnknownSize(node.iterator(), Spliterator.ORDERED), false)
.map(JsonNode::textValue)
.collect(Collectors.joining(" - "));
}
return ctxt.readTreeAsValue(node, Details.class);
}
}
public class Main {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper() {{
setSerializationInclusion(JsonInclude.Include.NON_NULL);
setDateFormat(new SimpleDateFormat("MM/dd/yyyy HH:mm:ss z", Locale.US));
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}};
String jsonPojo = """
{
"name": "John Smith",
"store": "Walmart",
"details": {
"dob": "1900/01/01",
"validated": true
}
}
""";
Customer<Serializable> c1 = mapper.readValue(jsonPojo, new TypeReference<Customer<Serializable>>() {});
System.out.println(c1.getDetails().getClass().getSimpleName()); //Prints Details
System.out.println(c1);
String jsonArray = """
{
"name": "John Smith",
"store": "Walmart",
"details": [
"Failed customer does not exist in the system", "Please contact administrator"
]
}
""";
Customer<Serializable> c2 = mapper.readValue(jsonArray, new TypeReference<Customer<Serializable>>() {});
System.out.println(c2.getDetails().getClass().getSimpleName()); //Prints String
System.out.println(c2);
}
}
Вот также демо-версия на OneCompiler, демонстрирующая приведенный выше код.
Это идеальное решение с подробным и полезным объяснением. Спасибо, что изменили вопрос, чтобы ваш ответ был более ясным.
@greatFritz Спасибо! Я только сейчас понял, что копируя код, я перепутал решение с другой попыткой. Класс MyDeserializer возвращал экземпляр Details в обоих случаях, тогда как основная часть теста не использовала TypeReference<Serializable> для чтения строк json. Я также обновил демо-версию.
Вы использовали общий тип
Tдляdetailsв классеCustomer, но в объявлении класса нет раздела параметров типа. Вы имели в видуprivate Details detailsвместоprivate T details?