Skip to content

Commit 4795221

Browse files
committed
Implement #38
1 parent 016a56f commit 4795221

File tree

6 files changed

+174
-21
lines changed

6 files changed

+174
-21
lines changed

release-notes/VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
Project: jackson-dataformat-xml
22
Version: 2.3.0 (xx-xxx-2013)
33

4+
#38: Support root-level Collection serialization
45
- Add support for `JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN`
56
- Improved indentation
67

src/main/java/com/fasterxml/jackson/dataformat/xml/ser/ToXmlGenerator.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,18 @@ public void finishWrappedValue(QName wrapperName, QName wrappedName) throws IOEx
324324
}
325325
}
326326

327+
/**
328+
* Trivial helper method called when to add a replicated wrapper name
329+
*
330+
* @since 2.2
331+
*/
332+
public void writeRepeatedFieldName() throws IOException, JsonGenerationException
333+
{
334+
if (_writeContext.writeFieldName(_nextName.getLocalPart()) == JsonWriteContext.STATUS_EXPECT_VALUE) {
335+
_reportError("Can not write a field name, expecting a value");
336+
}
337+
}
338+
327339
/*
328340
/**********************************************************
329341
/* JsonGenerator method overrides

src/main/java/com/fasterxml/jackson/dataformat/xml/ser/XmlBeanSerializer.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,14 @@ public BeanSerializer withObjectIdWriter(ObjectIdWriter objectIdWriter) {
121121
* extra handling, such as indication of whether to write attributes or
122122
* elements.
123123
*/
124-
@SuppressWarnings("deprecation")
125124
@Override
126125
protected void serializeFields(Object bean, JsonGenerator jgen0, SerializerProvider provider)
127126
throws IOException, JsonGenerationException
128127
{
129128
final ToXmlGenerator xgen = (ToXmlGenerator) jgen0;
130129
final BeanPropertyWriter[] props;
131130
// !!! TODO: change to use non-deprecated version in 2.3
132-
if (_filteredProps != null && provider.getSerializationView() != null) {
131+
if (_filteredProps != null && provider.getActiveView() != null) {
133132
props = _filteredProps;
134133
} else {
135134
props = _props;

src/main/java/com/fasterxml/jackson/dataformat/xml/ser/XmlSerializerProvider.java

Lines changed: 126 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
package com.fasterxml.jackson.dataformat.xml.ser;
22

33
import java.io.IOException;
4+
import java.util.Collection;
5+
46
import javax.xml.namespace.QName;
57
import javax.xml.stream.XMLStreamException;
68

79
import com.fasterxml.jackson.core.*;
810
import com.fasterxml.jackson.databind.JavaType;
11+
import com.fasterxml.jackson.databind.JsonMappingException;
912
import com.fasterxml.jackson.databind.JsonSerializer;
1013
import com.fasterxml.jackson.databind.SerializationConfig;
1114
import com.fasterxml.jackson.databind.ser.SerializerFactory;
1215
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
1316
import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
17+
import com.fasterxml.jackson.dataformat.xml.util.TypeUtil;
1418
import com.fasterxml.jackson.dataformat.xml.util.XmlRootNameLookup;
1519

1620

@@ -56,37 +60,141 @@ public DefaultSerializerProvider createInstance(SerializationConfig config,
5660
{
5761
return new XmlSerializerProvider(this, config, jsf);
5862
}
59-
63+
6064
@Override
6165
public void serializeValue(JsonGenerator jgen, Object value)
6266
throws IOException, JsonProcessingException
6367
{
64-
QName rootName = (value == null) ? ROOT_NAME_FOR_NULL
65-
: _rootNameLookup.findRootName(value.getClass(), _config);
68+
if (value == null) {
69+
_serializeNull(jgen);
70+
return;
71+
}
72+
Class<?> cls = value.getClass();
73+
QName rootName = _rootNameFromConfig();
74+
if (rootName == null) {
75+
rootName = _rootNameLookup.findRootName(cls, _config);
76+
}
6677
_initWithRootName(jgen, rootName);
67-
super.serializeValue(jgen, value);
68-
}
78+
final boolean asArray = Collection.class.isAssignableFrom(cls) ||
79+
(cls.isArray() && cls != byte[].class);
80+
if (asArray) {
81+
_startRootArray(jgen, rootName);
82+
}
83+
84+
// From super-class implementation
85+
final JsonSerializer<Object> ser = findTypedValueSerializer(cls, true, null);
86+
try {
87+
ser.serialize(value, jgen, this);
88+
} catch (IOException ioe) { // As per [JACKSON-99], pass IOException and subtypes as-is
89+
throw ioe;
90+
} catch (Exception e) { // but wrap RuntimeExceptions, to get path information
91+
String msg = e.getMessage();
92+
if (msg == null) {
93+
msg = "[no message for "+e.getClass().getName()+"]";
94+
}
95+
throw new JsonMappingException(msg, e);
96+
}
97+
// end of super-class implementation
6998

99+
if (asArray) {
100+
jgen.writeEndObject();
101+
}
102+
}
103+
70104
@Override
71105
public void serializeValue(JsonGenerator jgen, Object value, JavaType rootType)
72106
throws IOException, JsonProcessingException
73107
{
74-
QName rootName = _rootNameLookup.findRootName(rootType, _config);
108+
if (value == null) {
109+
_serializeNull(jgen);
110+
return;
111+
}
112+
QName rootName = _rootNameFromConfig();
113+
if (rootName == null) {
114+
rootName = _rootNameLookup.findRootName(rootType, _config);
115+
}
75116
_initWithRootName(jgen, rootName);
76-
super.serializeValue(jgen, value, rootType);
77-
}
117+
final boolean asArray = TypeUtil.isIndexedType(rootType);
118+
if (asArray) {
119+
_startRootArray(jgen, rootName);
120+
}
78121

122+
final JsonSerializer<Object> ser = findTypedValueSerializer(rootType, true, null);
123+
// From super-class implementation
124+
try {
125+
ser.serialize(value, jgen, this);
126+
} catch (IOException ioe) { // no wrapping for IO (and derived)
127+
throw ioe;
128+
} catch (Exception e) { // but others do need to be, to get path etc
129+
String msg = e.getMessage();
130+
if (msg == null) {
131+
msg = "[no message for "+e.getClass().getName()+"]";
132+
}
133+
throw new JsonMappingException(msg, e);
134+
}
135+
// end of super-class implementation
136+
137+
if (asArray) {
138+
jgen.writeEndObject();
139+
}
140+
}
141+
79142
// @since 2.1
80143
@Override
81144
public void serializeValue(JsonGenerator jgen, Object value, JavaType rootType,
82145
JsonSerializer<Object> ser)
83146
throws IOException, JsonGenerationException
84147
{
85-
QName rootName = _rootNameLookup.findRootName(rootType, _config);
148+
if (value == null) {
149+
_serializeNull(jgen);
150+
return;
151+
}
152+
QName rootName = _rootNameFromConfig();
153+
if (rootName == null) {
154+
rootName = _rootNameLookup.findRootName(rootType, _config);
155+
}
86156
_initWithRootName(jgen, rootName);
87-
super.serializeValue(jgen, value, rootType, ser);
157+
final boolean asArray = TypeUtil.isIndexedType(rootType);
158+
if (asArray) {
159+
_startRootArray(jgen, rootName);
160+
}
161+
if (ser == null) {
162+
ser = findTypedValueSerializer(rootType, true, null);
163+
}
164+
// From super-class implementation
165+
try {
166+
ser.serialize(value, jgen, this);
167+
} catch (IOException ioe) { // no wrapping for IO (and derived)
168+
throw ioe;
169+
} catch (Exception e) { // but others do need to be, to get path etc
170+
String msg = e.getMessage();
171+
if (msg == null) {
172+
msg = "[no message for "+e.getClass().getName()+"]";
173+
}
174+
throw new JsonMappingException(msg, e);
175+
}
176+
// end of super-class implementation
177+
if (asArray) {
178+
jgen.writeEndObject();
179+
}
88180
}
89-
181+
182+
protected void _startRootArray(JsonGenerator jgen, QName rootName)
183+
throws IOException, JsonProcessingException
184+
{
185+
jgen.writeStartObject();
186+
// Could repeat root name, but what's the point? How to customize?
187+
((ToXmlGenerator) jgen).writeFieldName("item");
188+
}
189+
190+
@Override
191+
protected void _serializeNull(JsonGenerator jgen)
192+
throws IOException, JsonProcessingException
193+
{
194+
_initWithRootName(jgen, ROOT_NAME_FOR_NULL);
195+
super.serializeValue(jgen, null);
196+
}
197+
90198
protected void _initWithRootName(JsonGenerator jgen, QName rootName)
91199
throws IOException, JsonProcessingException
92200
{
@@ -103,7 +211,7 @@ protected void _initWithRootName(JsonGenerator jgen, QName rootName)
103211
}
104212
xgen.initGenerator();
105213
String ns = rootName.getNamespaceURI();
106-
/* [Issue-26] If we just try writing root element with namespace,
214+
/* [Issue#26] If we just try writing root element with namespace,
107215
* we will get an explicit prefix. But we'd rather use the default
108216
* namespace, so let's try to force that.
109217
*/
@@ -115,4 +223,10 @@ protected void _initWithRootName(JsonGenerator jgen, QName rootName)
115223
}
116224
}
117225
}
226+
227+
protected QName _rootNameFromConfig()
228+
{
229+
String name = _config.getRootName();
230+
return (name == null) ? null : new QName(name);
231+
}
118232
}

src/test/java/com/fasterxml/jackson/dataformat/xml/XmlTestBase.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,14 @@ protected String readAll(File f) throws IOException
274274
return sb.toString();
275275
}
276276

277-
public String jaxbSerialized(Object ob) throws Exception
277+
public String jaxbSerialized(Object ob, Class<?>... classes) throws Exception
278278
{
279279
StringWriter sw = new StringWriter();
280-
javax.xml.bind.JAXB.marshal(ob, sw);
280+
if (classes.length == 0) {
281+
javax.xml.bind.JAXB.marshal(ob, sw);
282+
} else {
283+
javax.xml.bind.JAXBContext.newInstance(classes).createMarshaller().marshal(ob, sw);
284+
}
281285
sw.close();
282286
return sw.toString();
283287
}

src/test/java/com/fasterxml/jackson/dataformat/xml/failing/TestUnwrappedRootList.java

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.*;
44

55
import com.fasterxml.jackson.databind.AnnotationIntrospector;
6+
import com.fasterxml.jackson.databind.JavaType;
67
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
78
import com.fasterxml.jackson.dataformat.xml.*;
89

@@ -68,13 +69,35 @@ public void testListSerialization() throws Exception
6869
l.add(r2);
6970

7071
// to see what JAXB might do, uncomment:
71-
// System.out.println("By JAXB: "+jaxbSerialized(l));
72+
//System.out.println("By JAXB: "+jaxbSerialized(l)); // ArrayList.class, SampleResource.class));
7273

73-
String result = xmlMapper.writeValueAsString(l);
74-
assertNotNull(result);
74+
String xml = xmlMapper
75+
.writerWithDefaultPrettyPrinter()
76+
.withRootName("RootList")
77+
.writeValueAsString(l)
78+
.trim();
7579

76-
// TODO: verify actual contents
80+
// first trivial sanity checks
81+
assertNotNull(xml);
82+
if (xml.indexOf("<RootList>") < 0) {
83+
fail("Unexpected output: should have <RootList> as root element, got: "+xml);
84+
}
85+
86+
// and then try reading back
87+
JavaType resListType = xmlMapper.getTypeFactory()
88+
.constructCollectionType(List.class, SampleResource.class);
89+
Object ob = xmlMapper.reader(resListType).readValue(xml);
90+
assertNotNull(ob);
91+
92+
// System.err.println("XML -> "+xmlMapper.writerWithDefaultPrettyPrinter().writeValueAsString(ob));
7793

78-
// assertEquals("<x></x>", result);
94+
assertTrue(ob instanceof List);
95+
List<?> resultList = (List<?>) ob;
96+
assertEquals(2, resultList.size());
97+
assertEquals(SampleResource.class, resultList.get(0).getClass());
98+
assertEquals(SampleResource.class, resultList.get(1).getClass());
99+
SampleResource rr = (SampleResource) resultList.get(1);
100+
assertEquals("William", rr.getName());
101+
79102
}
80103
}

0 commit comments

Comments
 (0)