4

If there are multiple custom XML Serializers (XMLStreamWriter) in a class, serialization fails.

I have two classes: CustomClass1, CustomClass2. There is a wrapping class TestJacksonXml1. When I am trying to serialize TestJacksonXml1, it throws an exception.

CustomClass1

class CustomClass1 {
    int prop1;

    public CustomClass1(int prop1) {
        this.prop1 = prop1;
    }

    public int getProp1() {
        return prop1;
    }

    static class CustomClass1Serializer extends StdSerializer<CustomClass1> {
        public CustomClass1Serializer() { this(null); }

        public CustomClass1Serializer(Class<CustomClass1> t) {
            super(t);
        }

        @Override
        public void serialize(CustomClass1 customClass1, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            final ToXmlGenerator toXmlGenerator = (ToXmlGenerator) jsonGenerator;
            final XMLStreamWriter staxWriter = (toXmlGenerator).getStaxWriter();
            try {
                staxWriter.writeStartElement("class1");
                staxWriter.writeCharacters(String.valueOf(customClass1.prop1));
                staxWriter.writeEndElement();
            } catch (XMLStreamException e){
                e.printStackTrace();
            }
        }
    }
}

CustomClass2

class CustomClass2 {
        int prop2;

        public CustomClass2(int prop2) {
            this.prop2 = prop2;
        }

        public int getProp2() {
            return prop2;
        }

        static class CustomClass2Serializer extends StdSerializer<CustomClass2> {
            public CustomClass2Serializer() { this(null); }

            public CustomClass2Serializer(Class<CustomClass2> t) {
                super(t);
            }

            @Override
            public void serialize(CustomClass2 customClass2, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                final ToXmlGenerator toXmlGenerator = (ToXmlGenerator) jsonGenerator;
                final XMLStreamWriter staxWriter = (toXmlGenerator).getStaxWriter();
                try {
                    staxWriter.writeStartElement("class2");
                    staxWriter.writeCharacters(String.valueOf(customClass2.prop2));
                    staxWriter.writeEndElement();
                } catch (XMLStreamException e){
                    e.printStackTrace();
                }
            }
        }
    }

Enclosing Class

public class TestJacksonXml1 {
    @JsonSerialize(using = CustomClass1.CustomClass1Serializer.class)
    CustomClass1 obj1;

    @JsonSerialize(using = CustomClass2.CustomClass2Serializer.class)
    CustomClass2 obj2;

    public TestJacksonXml1(CustomClass1 obj1, CustomClass2 obj2) {
        this.obj1 = obj1;
        this.obj2 = obj2;
    }

    public CustomClass1 getObj1() {
        return obj1;
    }

    public CustomClass2 getObj2() {
        return obj2;
    }

    public static void main(String[] args) throws JsonProcessingException {
        XmlMapper xmlMapper = new XmlMapper();
        System.out.println(xmlMapper.writeValueAsString(new TestJacksonXml1(new CustomClass1(10), new CustomClass2(20))));
    }
}

The exception I get is

Exception in thread "main" com.fasterxml.jackson.core.JsonGenerationException: Can not write a field name, expecting a value
    at com.fasterxml.jackson.core.JsonGenerator._reportError(JsonGenerator.java:1961)
    at com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator.writeFieldName(ToXmlGenerator.java:435)
    at com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator.writeFieldName(ToXmlGenerator.java:577)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:725)
    at com.fasterxml.jackson.dataformat.xml.ser.XmlBeanSerializerBase.serializeFields(XmlBeanSerializerBase.java:202)
    at com.fasterxml.jackson.dataformat.xml.ser.XmlBeanSerializer.serialize(XmlBeanSerializer.java:117)
    at com.fasterxml.jackson.dataformat.xml.ser.XmlSerializerProvider.serializeValue(XmlSerializerProvider.java:107)
    at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3905)
    at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3219)
    at fk.reportsvc.common.TestJacksonXml1.main(TestJacksonXml1.java:39)

When I comment out one of the custom serializers, the other one works. Why is it so?

Shouldn't we use two/multiple custom serializers (with XMLStreamWriter) at once?

If I use JsonGenerator directly instead of XMLStreamWriter, then I am able to use both custom serializers at once.

PS: My actual business classes contain many fields that are to be converted into nested XML elements and attributes. Hence preferring XMLStreamWriter directly over others.

Ihor Patsian
  • 1,288
  • 2
  • 15
  • 25
mac
  • 627
  • 1
  • 9
  • 21
  • `If I use JsonGenerator directly instead of XMLStreamWriter, then I am able to use both custom serializers at once.` - in that case, why you do not want to use it and want to use `XMLStreamWriter`? Could you show the current output and what would you like to achieve? – Michał Ziober Jul 30 '19 at 18:56
  • JsonGenerator is hard to use. Too much boilerplate code and not so much documentation I could find (especially when XML is nested and with many attributes, properties) and XMLStreamWriter is StaxWriter2 which is widely used. – mac Jul 31 '19 at 06:45

1 Answers1

3

Much simpler would be to use methods from ToXmlGenerator class. Take a look at: setNextName, writeRaw and writeRepeatedFieldName methods. In your case, implementation could look like below:

class CustomClass2Serializer extends StdSerializer<CustomClass2> {

    private final QName name = new QName("class2");

    public CustomClass2Serializer() {
        super(CustomClass2.class);
    }

    @Override
    public void serialize(CustomClass2 customClass2, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        final ToXmlGenerator xmlGenerator = (ToXmlGenerator) jsonGenerator;
        xmlGenerator.setNextName(name);
        xmlGenerator.writeStartObject();
        xmlGenerator.writeRaw(String.valueOf(customClass2.prop2));
        xmlGenerator.writeRepeatedFieldName();
        xmlGenerator.writeEndObject();
    }
}

and:

class CustomClass1Serializer extends StdSerializer<CustomClass1> {

    private final QName name = new QName("class1");

    public CustomClass1Serializer() {
        super(CustomClass1.class);
    }

    @Override
    public void serialize(CustomClass1 customClass1, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        final ToXmlGenerator xmlGenerator = (ToXmlGenerator) jsonGenerator;
        xmlGenerator.setNextName(name);
        xmlGenerator.writeStartObject();
        xmlGenerator.writeRaw(String.valueOf(customClass1.prop1));
        xmlGenerator.writeRepeatedFieldName();
        xmlGenerator.writeEndObject();
    }
}

Generated XML should look like below:

<TestJacksonXml1>
  <class1>10</class1>
  <class2>20</class2>
</TestJacksonXml1>

Main problem in your case is you want to skip object node for CustomClass1 and CustomClass1 classes. Much simpler would be implementing serialiser for TestJacksonXml1 class:

class TestJacksonXml1JsonSerializer extends JsonSerializer<TestJacksonXml1> {
    @Override
    public void serialize(TestJacksonXml1 value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeStartObject();
        gen.writeStringField("class1", String.valueOf(value.obj1.prop1));
        gen.writeStringField("class2", String.valueOf(value.obj2.prop2));
        gen.writeEndObject();
    }
}

Now, TestJacksonXml1 class should look like below:

@JsonSerialize(using = TestJacksonXml1JsonSerializer.class)
class TestJacksonXml1 {

    CustomClass1 obj1;
    CustomClass2 obj2;
    // getters, setters, etc
}

It should generate the same output but is much easier to implement.

Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
  • My CustomClass1, CustomClass2 contains many fields which are to be serialized into XML attributes and elements in a nested manner. Using StaxWriter, its easy rather than JsonGenerator or ToXMLGenerator. Ex: In the above example, using ToXMLGenerator, it took 5 lines to write an XML element, where as in StaxWriter, we can achieve in 3 lines – mac Aug 01 '19 at 14:41