Сериализировать список в XML с Джексоном без аннотации? - PullRequest
0 голосов
/ 27 июня 2018

Я ищу способ (де) сериализации List предметов без использования аннотаций в Джексоне. Это возможно? До сих пор я пытаюсь заменить тег <item> тегом, указывающим на класс элемента, но безрезультатно. И даже если это сработало, я не уверен, предложит ли Джексон способ обработки этой информации тега.

Чтобы лучше понять, к чему я стремлюсь, вот пример:

public class JacksonTest {

    private static class ListElement {
        private boolean value;
        // getters, setters, constructors omitted
    }

    @Test
    public void testDeSerialization() throws Exception {
        final List<ListElement> existing = Arrays.asList(new ListElement(true));
        final ObjectMapper mapper = new XmlMapper();
        final JavaType listJavaType = mapper.getTypeFactory().constructCollectionType(List.class, ListElement.class);
        final String listString = mapper.writerFor(listJavaType).writeValueAsString(existing);
        System.out.println(listString);
        // "<List><item><value>true</value></item></List>"
    }

}

Итак, результат равен <List><item><value>true</value></item></List>, хотя я хочу, чтобы тег <item> был заменен (квалифицированным) именем класса или предлагался атрибут type. Конечно, даже это не помогло бы, если бы в Джексоне не было способа обработать это имя класса.

Здесь я зашел в тупик или есть путь?

Ответы [ 2 ]

0 голосов
/ 28 июня 2018

Хорошо, после некоторой обработки и отладки с предложением Evertude, я нашел решение. Я не очень доволен частью сериализации и, честно говоря, я не знаю, почему я должен был сделать это таким образом. При отладке я заметил, что XmlGenerator::setNextName требуется вызывать один раз, но это не влияет на следующий вызов, поэтому мне пришлось реализовать там переключатель и установить имя поля для следующего элемента в цикле напрямую.

Я был бы рад, если бы кто-то понял, что я делаю неправильно, но по крайней мере моя попытка пока работает:

@Test
public void testDeSerialization() throws Exception {
    final List<ListElement> existing = Arrays.asList(new ListElement(true), new ListElement(false));
    JacksonXmlModule module = new JacksonXmlModule();
    module.addSerializer(new MyListSerializer());
    final ObjectMapper mapper = new XmlMapper(module);
    final JavaType listJavaType = mapper.getTypeFactory().constructCollectionType(List.class, ListElement.class);
    final ObjectWriter writer = mapper.writerFor(listJavaType);
    final String listString = writer.writeValueAsString(existing);
    module.addDeserializer(List.class, new MyListDeserializer());
    List<ListElement> deserialized = mapper.readValue(listString, List.class);
    assertEquals(existing, deserialized); // provided there're proper hash() and equals() methods
}

private class MyListSerializer extends JsonSerializer<List> {

    @Override
    public void serialize(List list, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
            throws IOException {
        boolean done = false;
        for (Object obj : list) {
            if (jsonGenerator instanceof ToXmlGenerator) {
                ToXmlGenerator xmlGenerator = (ToXmlGenerator) jsonGenerator;
                String className = obj.getClass().getSimpleName();
                // weird switch
                if (!done) xmlGenerator.setNextName(new QName(className));
                else jsonGenerator.writeFieldName(className);
                done = true;
            }
            jsonGenerator.writeObject(obj);
        }
    }

    @Override
    public Class<List> handledType() {
        return List.class;
    }
}

private class MyListDeserializer extends JsonDeserializer<List> {

    @Override
    public List deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        List<Object> items = new ArrayList<>();
        JsonToken nextToken;
        while ((nextToken = p.nextToken()) != JsonToken.END_OBJECT) {
            String currentName = p.currentName();
            try {
                String className = "my.test.project.JacksonCustomSerializer$" + currentName;
                Class<?> loadClass = getClass().getClassLoader() // TODO put qualifier in ctxt?
                        .loadClass(className);
                p.nextToken();
                items.add(p.readValueAs(loadClass));
            } catch (ClassNotFoundException e) {
                // some handling
            }
        }
        return items;
    }

    @Override
    public Class<List> handledType() {
        return List.class;
    }
}
0 голосов
/ 27 июня 2018

Вы можете определить свой собственный JsonSerializer (также используется для XML) и добавить его в JacksonXmlModule.

ToXmlGenerator имеет функцию setNextName, которая позволяет переопределить имя элемента по умолчанию

private class MyListSerializer extends JsonSerializer<List> {
    @Override
    public void serialize(List list, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
            throws IOException {
        for (Object obj : list) {
            if (jsonGenerator instanceof ToXmlGenerator) {
                ToXmlGenerator xmlGenerator = (ToXmlGenerator) jsonGenerator;
                String className = obj.getClass().getSimpleName();
                xmlGenerator.setNextName(new QName(className));
            }
            jsonGenerator.writeObject(obj);
            // this is overridden at the next iteration
            // and ignored at the last
            jsonGenerator.writeFieldName("dummy");
        }
    }

    @Override
    public Class<List> handledType() {
        return List.class;
    }
}

@Test
public void testDeSerialization() throws Exception {
    final List<ListElement> existing = Arrays.asList(new ListElement(true));
    JacksonXmlModule module = new JacksonXmlModule();
    module.addSerializer(new MyListSerializer());
    final ObjectMapper mapper = new XmlMapper(module);
    final JavaType listJavaType = mapper.getTypeFactory().constructCollectionType(List.class, ListElement.class);
    final ObjectWriter writer = mapper.writerFor(listJavaType);
    final String listString = writer.writeValueAsString(existing);
    System.out.println(listString);
    // "<List><ListElement><value>true</value></ListElement></List>"
}
...