Джексон десериализует общий класс с полем T как массив POJO или String

У меня есть следующий класс

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);
}}; 

Вы использовали общий тип T для details в классе Customer, но в объявлении класса нет раздела параметров типа. Вы имели в виду private Details details вместо private T details?

dani-vta 28.06.2024 22:27

Кроме того, каков ожидаемый результат, когда details появляется в виде массива строк в json, объединяя строки в одну и присваивая ее свойству dob объекта Details?

dani-vta 28.06.2024 22:32

Я использую общий тип T для проверки в будущем, если вместо Details получу другой объект. Моим ожидаемым результатом для получения подробной информации будет сохранение массива json в виде одной строки в поле.

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

Ответы 1

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

Если до десериализации вы уже знали, что содержит 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 01.07.2024 23:20

@greatFritz Спасибо! Я только сейчас понял, что копируя код, я перепутал решение с другой попыткой. Класс MyDeserializer возвращал экземпляр Details в обоих случаях, тогда как основная часть теста не использовала TypeReference<Serializable> для чтения строк json. Я также обновил демо-версию.

dani-vta 02.07.2024 18:33

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