jackson-dataformat-xml icon indicating copy to clipboard operation
jackson-dataformat-xml copied to clipboard

@JacksonXmlProperty with attributes raises an exception when used with @JsonIdentityInfo

Open lightbringer opened this issue 7 years ago • 4 comments

I'm running into an issue with version 2.8.9, which might be related to issue #81 / #82: I'm trying to serialize an object graph with a circular reference using JsonIdentityInfo. This works well unless the referenced object has properties marked with @JacksonXmlProperty( isAttribute = true )

Minimal example below:

@JsonIdentityInfo( generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id" )
@JacksonXmlRootElement( localName = "a" )
public class A {
    public static void main( String[] args ) throws IOException {
        final A a = new A();
        a.b = new B(); 
        a.b.setA( a ); //B has a reference back to A
        
        final XmlMapper mapper = new XmlMapper();
        final String json = mapper.writeValueAsString( a );
        System.out.println( json );
    }

    private B b;

    private String name = "test";

    
    @JacksonXmlProperty( isAttribute = true ) //test runs with isAttribute set to false (but serializes name as an element)
    public String getName() {
        return name;
    }
 //other getter/setter omitted for brevity    

Expected output:

<a name="test">
<id>1</id>
<b>
<a>1</a>
</b>
</a>

Instead the exception:

 Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Unexpected IOException (of type java.io.IOException): javax.xml.stream.XMLStreamException: Trying to write an attribute when there is no open start element.
	at com.fasterxml.jackson.databind.JsonMappingException.fromUnexpectedIOE(JsonMappingException.java:333)
	at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3077)
	at test.A.main(A.java:20)

is thrown. Removing the JsonIdentityInfo annotation solves the issue as well, i.e. name is correctly serialized as an attribute, but then a circular reference causes a stack overflow - obviously.

lightbringer avatar Jun 29 '17 10:06 lightbringer

I'm running into a similar issue (using v2.8.9) when trying to customize the PrettyPrinter and serialize to xml. If I simplify what I'm attempting:

ObjectMapper mapper = new XmlMapper(xmlModule);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
DefaultPrettyPrinter p = new DefaultPrettyPrinter();
ObjectWriter writer = mapper.writer(p);
writer.writeValueAsString(myObject);

then I run into the same exception. If I remove the use of the PrettyPrinter as such:

ObjectMapper mapper = new XmlMapper(xmlModule);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
ObjectWriter writer = mapper.writer();
writer.writeValueAsString(myObject);

then the serialization occurs without error. Maybe this will give further insight into the issue.

j-stefani avatar Dec 19 '17 21:12 j-stefani

Thank you. Yes, I think the whitespace causes issues here -- white space is a problem with XML because while it 99% of the time is meaningless, and users/devs rarely rely on it, it has to be retained and can not be discarded in general, without knowing it is safe to do so. This means that XML parser will have to produce a text event, and then jackson xml module will try to figure out whether to drop it (most of the time), or retain for String value.

I don't know exact details of this specific case, but I am not surprised that this could be related.

cowtowncoder avatar Dec 19 '17 23:12 cowtowncoder

So, did anyone figure out how to make the make a the XmlMapper print using the customized PrettyPrinter?

vmrfreitas avatar Feb 07 '20 21:02 vmrfreitas

I found the answer, we need to use the Xml pretty printer and define a custom Xml indenter. An Indenter like the following would do the trick:

private static class MyIndenter
        implements DefaultXmlPrettyPrinter.Indenter, Serializable
    {

        private static final long serialVersionUID = 1L;
        @Override
        public void writeIndentation(final JsonGenerator p_jsonGenerator, final int p_level) throws IOException
        {
            p_jsonGenerator.writeRaw(new char[] { '\n' }, 0, 1);

            for (int l = 0; l < p_level; l++)
            {
                p_jsonGenerator.writeRaw(new char[] { ' ', ' ', ' ', ' ' }, 0, 4);
            }
        }
        @Override
        public void writeIndentation(final XMLStreamWriter2 p_xmlStreamWriter2, final int p_level) throws XMLStreamException
        {
            p_xmlStreamWriter2.writeRaw(new char[] { '\n' }, 0, 1);

            for (int l = 0; l < p_level; l++)
            {
                p_xmlStreamWriter2.writeRaw(new char[] { ' ', ' ', ' ', ' ' }, 0, 4);
            }
        }
        @Override
        public boolean isInline()
        {
            return false;
        }
    }

You can then use it in your XmlMapper:

final DefaultXmlPrettyPrinter xmlPrettyPrinter = new DefaultXmlPrettyPrinter();
final DefaultXmlPrettyPrinter.Indenter customIndenter = new MyIndenter();
xmlPrettyPrinter.indentObjectsWith(customIndenter);

new XmlMapper()
                         .writer(xmlPrettyPrinter)
                         .writeValueAsString(myObject)

jdfischer-cedreo avatar Jul 05 '22 10:07 jdfischer-cedreo