Skip to content

Commit

Permalink
Fix #441
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Jul 6, 2021
1 parent 97fe9eb commit 71c17d8
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 27 deletions.
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Project: jackson-dataformat-xml

2.13.0 (not yet released)

#441: Add `ToXmlGenerator.Feature.UNWRAP_ROOT_OBJECT_NODE` (to avoid
root `ObjectNode` wrapper element)
#442: Missing `START_OBJECT` token in complex element starting with text
(reported by richardsonwk@github)
#462: Remove `jackson-module-jaxb-annotations` runtime dependency (leave
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ public enum Feature implements FormatFeature

/**
* Feature that determines writing of root values of type {@code ObjectNode}
* ({@code JsonNode} that represents Object content values), regarding
* XML output.
* ({@code JsonNode} subtype that represents Object content values),
* regarding XML output.
* If enabled and {@code ObjectNode} has exactly one entry (key/value pair),
* then key of that entry is used as the root element name (and value
* is written as contents. Otherwise (if feature disabled, or if root
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package com.fasterxml.jackson.dataformat.xml.ser;

import java.io.IOException;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.SerializationConfig;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
import com.fasterxml.jackson.databind.util.TokenBuffer;

import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
import com.fasterxml.jackson.dataformat.xml.util.TypeUtil;
import com.fasterxml.jackson.dataformat.xml.util.XmlRootNameLookup;
Expand Down Expand Up @@ -86,6 +86,12 @@ public void serializeValue(JsonGenerator gen, Object value) throws IOException
if (xgen == null) { // called by convertValue()
asArray = false;
} else {
// [dataformat-xml#441]: allow ObjectNode unwrapping
if (_shouldUnwrapObjectNode(xgen, value)) {
_serializeUnwrappedObjectNode(xgen, value,
findTypedValueSerializer(cls, true, null));
return;
}
QName rootName = _rootNameFromConfig();
if (rootName == null) {
rootName = _rootNameLookup.findRootName(cls, _config);
Expand Down Expand Up @@ -116,7 +122,7 @@ public void serializeValue(JsonGenerator gen, Object value, JavaType rootType) t
{
serializeValue(gen, value, rootType, null);
}

// @since 2.1
@SuppressWarnings("resource")
@Override
Expand All @@ -137,6 +143,11 @@ public void serializeValue(JsonGenerator gen, Object value, JavaType rootType,
if (xgen == null) { // called by convertValue()
asArray = false;
} else {
// [dataformat-xml#441]: allow ObjectNode unwrapping
if (_shouldUnwrapObjectNode(xgen, value)) {
_serializeUnwrappedObjectNode(xgen, value, ser);
return;
}
QName rootName = _rootNameFromConfig();
if (rootName == null) {
rootName = _rootNameLookup.findRootName(rootType, _config);
Expand Down Expand Up @@ -211,7 +222,7 @@ public void serializePolymorphic(JsonGenerator gen, Object value, JavaType rootT
gen.writeEndObject();
}
}

protected void _serializeXmlNull(JsonGenerator gen) throws IOException
{
// 14-Nov-2016, tatu: As per [dataformat-xml#213], we may have explicitly
Expand Down Expand Up @@ -245,10 +256,9 @@ protected void _initWithRootName(ToXmlGenerator xgen, QName rootName) throws IOE
}
xgen.initGenerator();
String ns = rootName.getNamespaceURI();
/* [dataformat-xml#26] If we just try writing root element with namespace,
* we will get an explicit prefix. But we'd rather use the default
* namespace, so let's try to force that.
*/
// [dataformat-xml#26] If we just try writing root element with namespace,
// we will get an explicit prefix. But we'd rather use the default
// namespace, so let's try to force that.
if (ns != null && ns.length() > 0) {
try {
xgen.getStaxWriter().setDefaultNamespace(ns);
Expand All @@ -271,6 +281,35 @@ protected QName _rootNameFromConfig()
return new QName(ns, name.getSimpleName());
}

// @since 2.13
protected boolean _shouldUnwrapObjectNode(ToXmlGenerator xgen, Object value)
{
return xgen.isEnabled(ToXmlGenerator.Feature.UNWRAP_ROOT_OBJECT_NODE)
&& (value instanceof ObjectNode)
&& (((ObjectNode) value).size() == 1);
}

// @since 2.13
protected void _serializeUnwrappedObjectNode(ToXmlGenerator xgen, Object value,
JsonSerializer<Object> ser) throws IOException
{
ObjectNode root = (ObjectNode) value;
Map.Entry<String, JsonNode> entry = root.fields().next();
final JsonNode newRoot = entry.getValue();

// No namespace associated with JsonNode:
_initWithRootName(xgen, new QName(entry.getKey()));
if (ser == null) {
ser = findTypedValueSerializer(newRoot.getClass(), true, null);
}
// From super-class implementation
try {
ser.serialize(newRoot, xgen, this);
} catch (Exception e) { // but others do need to be, to get path etc
throw _wrapAsIOE(xgen, e);
}
}

protected ToXmlGenerator _asXmlGenerator(JsonGenerator gen)
throws JsonMappingException
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.fasterxml.jackson.dataformat.xml.failing;
package com.fasterxml.jackson.dataformat.xml.node;

import com.fasterxml.jackson.annotation.JsonUnwrapped;

Expand All @@ -10,9 +10,9 @@
import com.fasterxml.jackson.dataformat.xml.XmlTestBase;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;

// for [dataformat-xml#441]
public class JsonNodeBasicSer441Test extends XmlTestBase
public class JsonNodeSerUnwrapped441Test extends XmlTestBase
{
// [dataformat-xml#441]
static class Stuff441 {
@JsonUnwrapped
public ObjectNode node;
Expand All @@ -27,26 +27,42 @@ static class Stuff441 {
private final ObjectWriter XML_WRITER_UNWRAP = XML_MAPPER.writer()
.with(ToXmlGenerator.Feature.UNWRAP_ROOT_OBJECT_NODE);

public void testSimpleNode() throws Exception
// [dataformat-xml#441]: before changes, work-around should be fine
public void testOlderWorkaround() throws Exception
{
ObjectNode xml = XML_MAPPER.createObjectNode();
ObjectNode root = xml.putObject("root");
root.put("id", 13);
root.put("enabled", true);
ObjectNode child = xml.putObject("root");
child.put("id", 11);
child.put("enabled", false);

// Had to manually unwrap, set root name...
assertEquals("<root><id>11</id><enabled>false</enabled></root>",
XML_WRITER_WRAP.withRootName("root")
.writeValueAsString(child));
}

// [dataformat-xml#441]
public void testSimpleNode() throws Exception
{
ObjectNode root = XML_MAPPER.createObjectNode();
ObjectNode child = root.putObject("root");
child.put("id", 13);
child.put("enabled", true);

final String INNER = "<root><id>13</id><enabled>true</enabled></root>";

assertEquals("<ObjectNode>"+INNER+"</ObjectNode>",
XML_WRITER_WRAP.writeValueAsString(xml));
XML_WRITER_WRAP.writeValueAsString(root));
assertEquals(INNER,
XML_WRITER_UNWRAP.writeValueAsString(xml));
XML_WRITER_UNWRAP.writeValueAsString(root));
}

// [dataformat-xml#441]
public void testArrayInObjectNode() throws Exception
{
ObjectNode xml = XML_MAPPER.createObjectNode();
ObjectNode root = xml.putObject("root");
ArrayNode arr = root.putArray("array");
ObjectNode root = XML_MAPPER.createObjectNode();
ObjectNode child = root.putObject("root");
ArrayNode arr = child.putArray("array");
arr.add("first");
ObjectNode second = arr.addObject();
second.put("value", 137);
Expand All @@ -55,9 +71,9 @@ public void testArrayInObjectNode() throws Exception

//System.err.println("XML/array: "+XML_MAPPER.writeValueAsString(xml));
assertEquals("<ObjectNode>"+INNER+"</ObjectNode>",
XML_WRITER_WRAP.writeValueAsString(xml));
XML_WRITER_WRAP.writeValueAsString(root));
assertEquals(INNER,
XML_WRITER_UNWRAP.writeValueAsString(xml));
XML_WRITER_UNWRAP.writeValueAsString(root));
}

// 03-Jul-2021, tatu: Would be great to further support "unwrapping" of
Expand Down

0 comments on commit 71c17d8

Please sign in to comment.