Skip to content

Commit

Permalink
Added support for @JsonUnwrapped annotation. Fixes #83.
Browse files Browse the repository at this point in the history
  • Loading branch information
Pascal Gélinas committed Jan 30, 2014
1 parent ab84ac9 commit f64d69d
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package com.fasterxml.jackson.dataformat.xml.ser;

import java.io.IOException;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.*;
import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter;
import com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer;
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase;
import com.fasterxml.jackson.databind.util.NameTransformer;

/**
* Copy of {@link UnwrappingBeanSerializer} required to extend
* {@link XmlBeanSerializerBase} for XML-specific handling.
*
* @author Pascal Gélinas
*
*/
public class UnwrappingXmlBeanSerializer extends XmlBeanSerializerBase {
/**
* Transformer used to add prefix and/or suffix for properties of unwrapped
* POJO.
*/
protected final NameTransformer _nameTransformer;

/*
/**********************************************************
/* Life-cycle: constructors
/**********************************************************
*/

/**
* Constructor used for creating unwrapping instance of a standard
* <code>BeanSerializer</code>
*/
public UnwrappingXmlBeanSerializer(XmlBeanSerializerBase src, NameTransformer transformer)
{
super(src, transformer);
_nameTransformer = transformer;
}

public UnwrappingXmlBeanSerializer(UnwrappingXmlBeanSerializer src, ObjectIdWriter objectIdWriter)
{
super(src, objectIdWriter);
_nameTransformer = src._nameTransformer;
}

public UnwrappingXmlBeanSerializer(UnwrappingXmlBeanSerializer src, ObjectIdWriter objectIdWriter, Object filterId)
{
super(src, objectIdWriter, filterId);
_nameTransformer = src._nameTransformer;
}

protected UnwrappingXmlBeanSerializer(UnwrappingXmlBeanSerializer src, String[] toIgnore)
{
super(src, toIgnore);
_nameTransformer = src._nameTransformer;
}

/*
/**********************************************************
/* Life-cycle: factory methods, fluent factories
/**********************************************************
*/

@Override
public JsonSerializer<Object> unwrappingSerializer(NameTransformer transformer)
{
// !!! 23-Jan-2012, tatu: Should we chain transformers?
return new UnwrappingXmlBeanSerializer(this, transformer);
}

@Override
public boolean isUnwrappingSerializer()
{
return true; // sure is
}

@Override
public BeanSerializerBase withObjectIdWriter(ObjectIdWriter objectIdWriter)
{
return new UnwrappingXmlBeanSerializer(this, objectIdWriter);
}

@Override
protected BeanSerializerBase withFilterId(Object filterId)
{
return new UnwrappingXmlBeanSerializer(this, _objectIdWriter, filterId);
}

@Override
protected BeanSerializerBase withIgnorals(String[] toIgnore)
{
return new UnwrappingXmlBeanSerializer(this, toIgnore);
}

/**
* JSON Array output can not be done if unwrapping operation is requested;
* so implementation will simply return 'this'.
*/
@Override
protected BeanSerializerBase asArraySerializer()
{
return this;
}

/*
/**********************************************************
/* JsonSerializer implementation that differs between impls
/**********************************************************
*/

/**
* Main serialization method that will delegate actual output to configured
* {@link BeanPropertyWriter} instances.
*/
@Override
public final void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
if (_objectIdWriter != null) {
_serializeWithObjectId(bean, jgen, provider, false);
return;
}
if (_propertyFilterId != null) {
serializeFieldsFiltered(bean, jgen, provider);
} else {
serializeFields(bean, jgen, provider);
}
}

/*
/**********************************************************
/* Standard methods
/**********************************************************
*/

@Override
public String toString()
{
return "UnwrappingXmlBeanSerializer for " + handledType().getName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ public XmlBeanSerializer(XmlBeanSerializerBase src, String[] toIgnore)

@Override
public JsonSerializer<Object> unwrappingSerializer(NameTransformer unwrapper) {
// return new UnwrappingBeanSerializer(this, unwrapper);
throw new UnsupportedOperationException("Unwrapping serialization not yet supported for XML");
return new UnwrappingXmlBeanSerializer(this, unwrapper);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ protected void serializeFields(Object bean, JsonGenerator jgen0, SerializerProvi
}

final int attrCount = _attributeCount;
boolean isAttribute = xgen._nextIsAttribute;
if (attrCount > 0) {
xgen.setNextIsAttribute(true);
}
Expand All @@ -157,7 +158,9 @@ protected void serializeFields(Object bean, JsonGenerator jgen0, SerializerProvi

try {
for (final int len = props.length; i < len; ++i) {
if (i == attrCount) {
// 28-jan-2014, pascal: we don't want to reset the attribute flag if we are an unwrapping serializer
// that started with nextIsAttribute to true because all properties should be unwrapped as attributes too.
if (i == attrCount && !(isAttribute && isUnwrappingSerializer())) {
xgen.setNextIsAttribute(false);
}
// also: if this is property to write as text ("unwrap"), need to:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package com.fasterxml.jackson.dataformat.xml;

import static org.junit.Assert.*;

import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;

// for #12
public class TestUnwrappingWithXML extends XmlTestBase
{
@JsonPropertyOrder({"x", "y"})
final static class Location {
public int x;
public int y;

public Location() { }
public Location(int x, int y) {
this.x = x;
this.y = y;
}
}

// IMPORTANT: ordering DOES matter here
@JsonPropertyOrder({ "name", "location" })
static class Unwrapping {
public String name;
@JsonUnwrapped(prefix="loc.")
public Location location;

public Unwrapping() { }
public Unwrapping(String str, int x, int y) {
name = str;
location = new Location(x, y);
}
}

static class UnwrappingWithAttributes{
@JacksonXmlProperty(isAttribute=true)
public String name;
@JacksonXmlProperty(isAttribute=true)
@JsonUnwrapped(prefix="loc.")
public Location location;

public UnwrappingWithAttributes() { }
public UnwrappingWithAttributes(String str, int x, int y) {
name = str;
location = new Location(x, y);
}
}

static class UnwrappingSubWithAttributes{
@JacksonXmlProperty(isAttribute=true)
public String name;
@JsonUnwrapped(prefix="loc.")
public LocationWithAttributes location;

public UnwrappingSubWithAttributes() { }
public UnwrappingSubWithAttributes(String str, int x, int y) {
name = str;
location = new LocationWithAttributes(x, y);
}
}

@JsonPropertyOrder({"x", "y"})
final static class LocationWithAttributes {
@JacksonXmlProperty(isAttribute=true)
public int x;
@JacksonXmlProperty(isAttribute=true)
public int y;

public LocationWithAttributes() { }
public LocationWithAttributes(int x, int y) {
this.x = x;
this.y = y;
}
}

/*
/**********************************************************
/* Tests
/**********************************************************
*/

/**
* Simple test to verify that explicit schema mapping works fine
* with unwrapped entities
*/
public void testSimpleUnwrappingRoundtrip()
throws Exception
{
final String XML = "<Unwrapping><name>Joe</name><loc.x>15</loc.x><loc.y>27</loc.y></Unwrapping>";
ObjectMapper mapper = xmlMapper(false);
Unwrapping wrapper = mapper.reader(Unwrapping.class).readValue(XML);
assertNotNull(wrapper);
assertNotNull(wrapper.location);
assertEquals(15, wrapper.location.x);
assertEquals(27, wrapper.location.y);

// should also write out the same way
assertEquals(XML, mapper.writerWithType(Unwrapping.class).writeValueAsString(wrapper));
}

public void testUnwrappingWithAttribute()
throws Exception
{
final String XML = "<UnwrappingWithAttributes name=\"Joe\" loc.x=\"15\" loc.y=\"27\"/>";
ObjectMapper mapper = xmlMapper(false);
UnwrappingWithAttributes wrapper = mapper.reader(UnwrappingWithAttributes.class).readValue(XML);
assertNotNull(wrapper);
assertNotNull(wrapper.location);
assertEquals(15, wrapper.location.x);
assertEquals(27, wrapper.location.y);

// should also write out the same way
assertEquals(XML, mapper.writerWithType(UnwrappingWithAttributes.class).writeValueAsString(wrapper));
}

public void testUnwrappingSubWithAttribute()
throws Exception
{
final String XML = "<UnwrappingSubWithAttributes name=\"Joe\" loc.x=\"15\" loc.y=\"27\"/>";
ObjectMapper mapper = xmlMapper(false);
UnwrappingSubWithAttributes wrapper = mapper.reader(UnwrappingSubWithAttributes.class).readValue(XML);
assertNotNull(wrapper);
assertNotNull(wrapper.location);
assertEquals(15, wrapper.location.x);
assertEquals(27, wrapper.location.y);

// should also write out the same way
assertEquals(XML, mapper.writerWithType(UnwrappingSubWithAttributes.class).writeValueAsString(wrapper));
}
}

0 comments on commit f64d69d

Please sign in to comment.