jackson-dataformat-xml
jackson-dataformat-xml copied to clipboard
@JacksonXmlProperty with attributes raises an exception when used with @JsonIdentityInfo
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.
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.
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.
So, did anyone figure out how to make the make a the XmlMapper print using the customized PrettyPrinter?
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)