Skip to content

Commit f64d69d

Browse files
author
Pascal Gélinas
committed
Added support for @JsonUnwrapped annotation. Fixes #83.
1 parent ab84ac9 commit f64d69d

File tree

4 files changed

+285
-3
lines changed

4 files changed

+285
-3
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package com.fasterxml.jackson.dataformat.xml.ser;
2+
3+
import java.io.IOException;
4+
5+
import com.fasterxml.jackson.core.*;
6+
import com.fasterxml.jackson.databind.*;
7+
import com.fasterxml.jackson.databind.ser.*;
8+
import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter;
9+
import com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer;
10+
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase;
11+
import com.fasterxml.jackson.databind.util.NameTransformer;
12+
13+
/**
14+
* Copy of {@link UnwrappingBeanSerializer} required to extend
15+
* {@link XmlBeanSerializerBase} for XML-specific handling.
16+
*
17+
* @author Pascal Gélinas
18+
*
19+
*/
20+
public class UnwrappingXmlBeanSerializer extends XmlBeanSerializerBase {
21+
/**
22+
* Transformer used to add prefix and/or suffix for properties of unwrapped
23+
* POJO.
24+
*/
25+
protected final NameTransformer _nameTransformer;
26+
27+
/*
28+
/**********************************************************
29+
/* Life-cycle: constructors
30+
/**********************************************************
31+
*/
32+
33+
/**
34+
* Constructor used for creating unwrapping instance of a standard
35+
* <code>BeanSerializer</code>
36+
*/
37+
public UnwrappingXmlBeanSerializer(XmlBeanSerializerBase src, NameTransformer transformer)
38+
{
39+
super(src, transformer);
40+
_nameTransformer = transformer;
41+
}
42+
43+
public UnwrappingXmlBeanSerializer(UnwrappingXmlBeanSerializer src, ObjectIdWriter objectIdWriter)
44+
{
45+
super(src, objectIdWriter);
46+
_nameTransformer = src._nameTransformer;
47+
}
48+
49+
public UnwrappingXmlBeanSerializer(UnwrappingXmlBeanSerializer src, ObjectIdWriter objectIdWriter, Object filterId)
50+
{
51+
super(src, objectIdWriter, filterId);
52+
_nameTransformer = src._nameTransformer;
53+
}
54+
55+
protected UnwrappingXmlBeanSerializer(UnwrappingXmlBeanSerializer src, String[] toIgnore)
56+
{
57+
super(src, toIgnore);
58+
_nameTransformer = src._nameTransformer;
59+
}
60+
61+
/*
62+
/**********************************************************
63+
/* Life-cycle: factory methods, fluent factories
64+
/**********************************************************
65+
*/
66+
67+
@Override
68+
public JsonSerializer<Object> unwrappingSerializer(NameTransformer transformer)
69+
{
70+
// !!! 23-Jan-2012, tatu: Should we chain transformers?
71+
return new UnwrappingXmlBeanSerializer(this, transformer);
72+
}
73+
74+
@Override
75+
public boolean isUnwrappingSerializer()
76+
{
77+
return true; // sure is
78+
}
79+
80+
@Override
81+
public BeanSerializerBase withObjectIdWriter(ObjectIdWriter objectIdWriter)
82+
{
83+
return new UnwrappingXmlBeanSerializer(this, objectIdWriter);
84+
}
85+
86+
@Override
87+
protected BeanSerializerBase withFilterId(Object filterId)
88+
{
89+
return new UnwrappingXmlBeanSerializer(this, _objectIdWriter, filterId);
90+
}
91+
92+
@Override
93+
protected BeanSerializerBase withIgnorals(String[] toIgnore)
94+
{
95+
return new UnwrappingXmlBeanSerializer(this, toIgnore);
96+
}
97+
98+
/**
99+
* JSON Array output can not be done if unwrapping operation is requested;
100+
* so implementation will simply return 'this'.
101+
*/
102+
@Override
103+
protected BeanSerializerBase asArraySerializer()
104+
{
105+
return this;
106+
}
107+
108+
/*
109+
/**********************************************************
110+
/* JsonSerializer implementation that differs between impls
111+
/**********************************************************
112+
*/
113+
114+
/**
115+
* Main serialization method that will delegate actual output to configured
116+
* {@link BeanPropertyWriter} instances.
117+
*/
118+
@Override
119+
public final void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider)
120+
throws IOException, JsonGenerationException
121+
{
122+
if (_objectIdWriter != null) {
123+
_serializeWithObjectId(bean, jgen, provider, false);
124+
return;
125+
}
126+
if (_propertyFilterId != null) {
127+
serializeFieldsFiltered(bean, jgen, provider);
128+
} else {
129+
serializeFields(bean, jgen, provider);
130+
}
131+
}
132+
133+
/*
134+
/**********************************************************
135+
/* Standard methods
136+
/**********************************************************
137+
*/
138+
139+
@Override
140+
public String toString()
141+
{
142+
return "UnwrappingXmlBeanSerializer for " + handledType().getName();
143+
}
144+
}

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
@@ -53,8 +53,7 @@ public XmlBeanSerializer(XmlBeanSerializerBase src, String[] toIgnore)
5353

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

6059
@Override

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ protected void serializeFields(Object bean, JsonGenerator jgen0, SerializerProvi
148148
}
149149

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

158159
try {
159160
for (final int len = props.length; i < len; ++i) {
160-
if (i == attrCount) {
161+
// 28-jan-2014, pascal: we don't want to reset the attribute flag if we are an unwrapping serializer
162+
// that started with nextIsAttribute to true because all properties should be unwrapped as attributes too.
163+
if (i == attrCount && !(isAttribute && isUnwrappingSerializer())) {
161164
xgen.setNextIsAttribute(false);
162165
}
163166
// also: if this is property to write as text ("unwrap"), need to:
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package com.fasterxml.jackson.dataformat.xml;
2+
3+
import static org.junit.Assert.*;
4+
5+
import org.junit.Test;
6+
7+
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
8+
import com.fasterxml.jackson.annotation.JsonUnwrapped;
9+
import com.fasterxml.jackson.databind.ObjectMapper;
10+
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
11+
12+
// for #12
13+
public class TestUnwrappingWithXML extends XmlTestBase
14+
{
15+
@JsonPropertyOrder({"x", "y"})
16+
final static class Location {
17+
public int x;
18+
public int y;
19+
20+
public Location() { }
21+
public Location(int x, int y) {
22+
this.x = x;
23+
this.y = y;
24+
}
25+
}
26+
27+
// IMPORTANT: ordering DOES matter here
28+
@JsonPropertyOrder({ "name", "location" })
29+
static class Unwrapping {
30+
public String name;
31+
@JsonUnwrapped(prefix="loc.")
32+
public Location location;
33+
34+
public Unwrapping() { }
35+
public Unwrapping(String str, int x, int y) {
36+
name = str;
37+
location = new Location(x, y);
38+
}
39+
}
40+
41+
static class UnwrappingWithAttributes{
42+
@JacksonXmlProperty(isAttribute=true)
43+
public String name;
44+
@JacksonXmlProperty(isAttribute=true)
45+
@JsonUnwrapped(prefix="loc.")
46+
public Location location;
47+
48+
public UnwrappingWithAttributes() { }
49+
public UnwrappingWithAttributes(String str, int x, int y) {
50+
name = str;
51+
location = new Location(x, y);
52+
}
53+
}
54+
55+
static class UnwrappingSubWithAttributes{
56+
@JacksonXmlProperty(isAttribute=true)
57+
public String name;
58+
@JsonUnwrapped(prefix="loc.")
59+
public LocationWithAttributes location;
60+
61+
public UnwrappingSubWithAttributes() { }
62+
public UnwrappingSubWithAttributes(String str, int x, int y) {
63+
name = str;
64+
location = new LocationWithAttributes(x, y);
65+
}
66+
}
67+
68+
@JsonPropertyOrder({"x", "y"})
69+
final static class LocationWithAttributes {
70+
@JacksonXmlProperty(isAttribute=true)
71+
public int x;
72+
@JacksonXmlProperty(isAttribute=true)
73+
public int y;
74+
75+
public LocationWithAttributes() { }
76+
public LocationWithAttributes(int x, int y) {
77+
this.x = x;
78+
this.y = y;
79+
}
80+
}
81+
82+
/*
83+
/**********************************************************
84+
/* Tests
85+
/**********************************************************
86+
*/
87+
88+
/**
89+
* Simple test to verify that explicit schema mapping works fine
90+
* with unwrapped entities
91+
*/
92+
public void testSimpleUnwrappingRoundtrip()
93+
throws Exception
94+
{
95+
final String XML = "<Unwrapping><name>Joe</name><loc.x>15</loc.x><loc.y>27</loc.y></Unwrapping>";
96+
ObjectMapper mapper = xmlMapper(false);
97+
Unwrapping wrapper = mapper.reader(Unwrapping.class).readValue(XML);
98+
assertNotNull(wrapper);
99+
assertNotNull(wrapper.location);
100+
assertEquals(15, wrapper.location.x);
101+
assertEquals(27, wrapper.location.y);
102+
103+
// should also write out the same way
104+
assertEquals(XML, mapper.writerWithType(Unwrapping.class).writeValueAsString(wrapper));
105+
}
106+
107+
public void testUnwrappingWithAttribute()
108+
throws Exception
109+
{
110+
final String XML = "<UnwrappingWithAttributes name=\"Joe\" loc.x=\"15\" loc.y=\"27\"/>";
111+
ObjectMapper mapper = xmlMapper(false);
112+
UnwrappingWithAttributes wrapper = mapper.reader(UnwrappingWithAttributes.class).readValue(XML);
113+
assertNotNull(wrapper);
114+
assertNotNull(wrapper.location);
115+
assertEquals(15, wrapper.location.x);
116+
assertEquals(27, wrapper.location.y);
117+
118+
// should also write out the same way
119+
assertEquals(XML, mapper.writerWithType(UnwrappingWithAttributes.class).writeValueAsString(wrapper));
120+
}
121+
122+
public void testUnwrappingSubWithAttribute()
123+
throws Exception
124+
{
125+
final String XML = "<UnwrappingSubWithAttributes name=\"Joe\" loc.x=\"15\" loc.y=\"27\"/>";
126+
ObjectMapper mapper = xmlMapper(false);
127+
UnwrappingSubWithAttributes wrapper = mapper.reader(UnwrappingSubWithAttributes.class).readValue(XML);
128+
assertNotNull(wrapper);
129+
assertNotNull(wrapper.location);
130+
assertEquals(15, wrapper.location.x);
131+
assertEquals(27, wrapper.location.y);
132+
133+
// should also write out the same way
134+
assertEquals(XML, mapper.writerWithType(UnwrappingSubWithAttributes.class).writeValueAsString(wrapper));
135+
}
136+
}

0 commit comments

Comments
 (0)