Skip to content

Commit

Permalink
Implement #38
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Aug 15, 2013
1 parent 016a56f commit 4795221
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 21 deletions.
1 change: 1 addition & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Project: jackson-dataformat-xml
Version: 2.3.0 (xx-xxx-2013)

#38: Support root-level Collection serialization
- Add support for `JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN`
- Improved indentation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,18 @@ public void finishWrappedValue(QName wrapperName, QName wrappedName) throws IOEx
}
}

/**
* Trivial helper method called when to add a replicated wrapper name
*
* @since 2.2
*/
public void writeRepeatedFieldName() throws IOException, JsonGenerationException
{
if (_writeContext.writeFieldName(_nextName.getLocalPart()) == JsonWriteContext.STATUS_EXPECT_VALUE) {
_reportError("Can not write a field name, expecting a value");
}
}

/*
/**********************************************************
/* JsonGenerator method overrides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,14 @@ public BeanSerializer withObjectIdWriter(ObjectIdWriter objectIdWriter) {
* extra handling, such as indication of whether to write attributes or
* elements.
*/
@SuppressWarnings("deprecation")
@Override
protected void serializeFields(Object bean, JsonGenerator jgen0, SerializerProvider provider)
throws IOException, JsonGenerationException
{
final ToXmlGenerator xgen = (ToXmlGenerator) jgen0;
final BeanPropertyWriter[] props;
// !!! TODO: change to use non-deprecated version in 2.3
if (_filteredProps != null && provider.getSerializationView() != null) {
if (_filteredProps != null && provider.getActiveView() != null) {
props = _filteredProps;
} else {
props = _props;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package com.fasterxml.jackson.dataformat.xml.ser;

import java.io.IOException;
import java.util.Collection;

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.SerializationConfig;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
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 @@ -56,37 +60,141 @@ public DefaultSerializerProvider createInstance(SerializationConfig config,
{
return new XmlSerializerProvider(this, config, jsf);
}

@Override
public void serializeValue(JsonGenerator jgen, Object value)
throws IOException, JsonProcessingException
{
QName rootName = (value == null) ? ROOT_NAME_FOR_NULL
: _rootNameLookup.findRootName(value.getClass(), _config);
if (value == null) {
_serializeNull(jgen);
return;
}
Class<?> cls = value.getClass();
QName rootName = _rootNameFromConfig();
if (rootName == null) {
rootName = _rootNameLookup.findRootName(cls, _config);
}
_initWithRootName(jgen, rootName);
super.serializeValue(jgen, value);
}
final boolean asArray = Collection.class.isAssignableFrom(cls) ||
(cls.isArray() && cls != byte[].class);
if (asArray) {
_startRootArray(jgen, rootName);
}

// From super-class implementation
final JsonSerializer<Object> ser = findTypedValueSerializer(cls, true, null);
try {
ser.serialize(value, jgen, this);
} catch (IOException ioe) { // As per [JACKSON-99], pass IOException and subtypes as-is
throw ioe;
} catch (Exception e) { // but wrap RuntimeExceptions, to get path information
String msg = e.getMessage();
if (msg == null) {
msg = "[no message for "+e.getClass().getName()+"]";
}
throw new JsonMappingException(msg, e);
}
// end of super-class implementation

if (asArray) {
jgen.writeEndObject();
}
}

@Override
public void serializeValue(JsonGenerator jgen, Object value, JavaType rootType)
throws IOException, JsonProcessingException
{
QName rootName = _rootNameLookup.findRootName(rootType, _config);
if (value == null) {
_serializeNull(jgen);
return;
}
QName rootName = _rootNameFromConfig();
if (rootName == null) {
rootName = _rootNameLookup.findRootName(rootType, _config);
}
_initWithRootName(jgen, rootName);
super.serializeValue(jgen, value, rootType);
}
final boolean asArray = TypeUtil.isIndexedType(rootType);
if (asArray) {
_startRootArray(jgen, rootName);
}

final JsonSerializer<Object> ser = findTypedValueSerializer(rootType, true, null);
// From super-class implementation
try {
ser.serialize(value, jgen, this);
} catch (IOException ioe) { // no wrapping for IO (and derived)
throw ioe;
} catch (Exception e) { // but others do need to be, to get path etc
String msg = e.getMessage();
if (msg == null) {
msg = "[no message for "+e.getClass().getName()+"]";
}
throw new JsonMappingException(msg, e);
}
// end of super-class implementation

if (asArray) {
jgen.writeEndObject();
}
}

// @since 2.1
@Override
public void serializeValue(JsonGenerator jgen, Object value, JavaType rootType,
JsonSerializer<Object> ser)
throws IOException, JsonGenerationException
{
QName rootName = _rootNameLookup.findRootName(rootType, _config);
if (value == null) {
_serializeNull(jgen);
return;
}
QName rootName = _rootNameFromConfig();
if (rootName == null) {
rootName = _rootNameLookup.findRootName(rootType, _config);
}
_initWithRootName(jgen, rootName);
super.serializeValue(jgen, value, rootType, ser);
final boolean asArray = TypeUtil.isIndexedType(rootType);
if (asArray) {
_startRootArray(jgen, rootName);
}
if (ser == null) {
ser = findTypedValueSerializer(rootType, true, null);
}
// From super-class implementation
try {
ser.serialize(value, jgen, this);
} catch (IOException ioe) { // no wrapping for IO (and derived)
throw ioe;
} catch (Exception e) { // but others do need to be, to get path etc
String msg = e.getMessage();
if (msg == null) {
msg = "[no message for "+e.getClass().getName()+"]";
}
throw new JsonMappingException(msg, e);
}
// end of super-class implementation
if (asArray) {
jgen.writeEndObject();
}
}


protected void _startRootArray(JsonGenerator jgen, QName rootName)
throws IOException, JsonProcessingException
{
jgen.writeStartObject();
// Could repeat root name, but what's the point? How to customize?
((ToXmlGenerator) jgen).writeFieldName("item");
}

@Override
protected void _serializeNull(JsonGenerator jgen)
throws IOException, JsonProcessingException
{
_initWithRootName(jgen, ROOT_NAME_FOR_NULL);
super.serializeValue(jgen, null);
}

protected void _initWithRootName(JsonGenerator jgen, QName rootName)
throws IOException, JsonProcessingException
{
Expand All @@ -103,7 +211,7 @@ protected void _initWithRootName(JsonGenerator jgen, QName rootName)
}
xgen.initGenerator();
String ns = rootName.getNamespaceURI();
/* [Issue-26] If we just try writing root element with namespace,
/* [Issue#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.
*/
Expand All @@ -115,4 +223,10 @@ protected void _initWithRootName(JsonGenerator jgen, QName rootName)
}
}
}

protected QName _rootNameFromConfig()
{
String name = _config.getRootName();
return (name == null) ? null : new QName(name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,14 @@ protected String readAll(File f) throws IOException
return sb.toString();
}

public String jaxbSerialized(Object ob) throws Exception
public String jaxbSerialized(Object ob, Class<?>... classes) throws Exception
{
StringWriter sw = new StringWriter();
javax.xml.bind.JAXB.marshal(ob, sw);
if (classes.length == 0) {
javax.xml.bind.JAXB.marshal(ob, sw);
} else {
javax.xml.bind.JAXBContext.newInstance(classes).createMarshaller().marshal(ob, sw);
}
sw.close();
return sw.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.*;

import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.dataformat.xml.*;

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

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

String result = xmlMapper.writeValueAsString(l);
assertNotNull(result);
String xml = xmlMapper
.writerWithDefaultPrettyPrinter()
.withRootName("RootList")
.writeValueAsString(l)
.trim();

// TODO: verify actual contents
// first trivial sanity checks
assertNotNull(xml);
if (xml.indexOf("<RootList>") < 0) {
fail("Unexpected output: should have <RootList> as root element, got: "+xml);
}

// and then try reading back
JavaType resListType = xmlMapper.getTypeFactory()
.constructCollectionType(List.class, SampleResource.class);
Object ob = xmlMapper.reader(resListType).readValue(xml);
assertNotNull(ob);

// System.err.println("XML -> "+xmlMapper.writerWithDefaultPrettyPrinter().writeValueAsString(ob));

// assertEquals("<x></x>", result);
assertTrue(ob instanceof List);
List<?> resultList = (List<?>) ob;
assertEquals(2, resultList.size());
assertEquals(SampleResource.class, resultList.get(0).getClass());
assertEquals(SampleResource.class, resultList.get(1).getClass());
SampleResource rr = (SampleResource) resultList.get(1);
assertEquals("William", rr.getName());

}
}

0 comments on commit 4795221

Please sign in to comment.