Skip to content

Commit 56dbc88

Browse files
committed
fix issue FasterXML#205 - do not swallow duplicated elements in xml>object deser
Signed-off-by: joaovarandas <[email protected]>
1 parent 98a9ff2 commit 56dbc88

File tree

3 files changed

+114
-3
lines changed

3 files changed

+114
-3
lines changed

src/main/java/com/fasterxml/jackson/dataformat/xml/JacksonXmlModule.java

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.fasterxml.jackson.databind.module.SimpleModule;
55
import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser;
66
import com.fasterxml.jackson.dataformat.xml.deser.XmlBeanDeserializerModifier;
7+
import com.fasterxml.jackson.dataformat.xml.deser.XmlUntypedObjectDeserializer;
78
import com.fasterxml.jackson.dataformat.xml.ser.XmlBeanSerializerModifier;
89

910
/**
@@ -65,6 +66,9 @@ public void setupModule(SetupContext context)
6566
XmlMapper m = (XmlMapper) context.getOwner();
6667
m.setXMLTextElementName(_cfgNameForTextElement);
6768
}
69+
70+
// fix the duplicated elements bug in an untyped Object
71+
addDeserializer(Object.class, new XmlUntypedObjectDeserializer());
6872

6973
/* Usually this would be the first call; but here anything added will
7074
* be stuff user may has added, so do it afterwards instead.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.fasterxml.jackson.dataformat.xml.deser;
2+
3+
import java.io.IOException;
4+
import java.util.ArrayList;
5+
import java.util.LinkedHashMap;
6+
import java.util.LinkedHashSet;
7+
import java.util.List;
8+
import java.util.Set;
9+
10+
import com.fasterxml.jackson.core.JsonParser;
11+
import com.fasterxml.jackson.core.JsonToken;
12+
import com.fasterxml.jackson.databind.DeserializationContext;
13+
import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer;
14+
15+
public class XmlUntypedObjectDeserializer extends UntypedObjectDeserializer {
16+
17+
@Override
18+
@SuppressWarnings("unchecked")
19+
protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOException {
20+
String firstKey;
21+
22+
JsonToken t = p.getCurrentToken();
23+
24+
if (t == JsonToken.START_OBJECT) {
25+
firstKey = p.nextFieldName();
26+
} else if (t == JsonToken.FIELD_NAME) {
27+
firstKey = p.getCurrentName();
28+
} else {
29+
if (t != JsonToken.END_OBJECT) {
30+
throw ctxt.mappingException(handledType(), p.getCurrentToken());
31+
}
32+
firstKey = null;
33+
}
34+
35+
// empty map might work; but caller may want to modify... so better just give small modifiable
36+
LinkedHashMap<String, Object> resultMap = new LinkedHashMap<String, Object>(2);
37+
if (firstKey == null) return resultMap;
38+
39+
p.nextToken();
40+
resultMap.put(firstKey, deserialize(p, ctxt));
41+
42+
43+
// 03-Aug-2016, jpvarandas: handle next objects and create an array
44+
Set<String> listKeys = new LinkedHashSet<>();
45+
46+
String nextKey;
47+
while((nextKey = p.nextFieldName()) != null) {
48+
p.nextToken();
49+
if (resultMap.containsKey(nextKey)) {
50+
Object listObject = resultMap.get(nextKey);
51+
52+
if (!(listObject instanceof List)) {
53+
listObject = new ArrayList<>();
54+
((List) listObject).add(resultMap.get(nextKey));
55+
56+
resultMap.put(nextKey, listObject);
57+
}
58+
59+
((List) listObject).add(deserialize(p, ctxt));
60+
61+
listKeys.add(nextKey);
62+
63+
} else {
64+
resultMap.put(nextKey, deserialize(p, ctxt));
65+
66+
}
67+
}
68+
69+
/*
70+
// if we have more than 1 element type in this root, it
71+
// makes sense (semantically) to fix the object name to a nList
72+
if (resultMap.keySet().size() > 1 && !listKeys.isEmpty()) {
73+
for(String key: listKeys) {
74+
Object data = resultMap.get(key);
75+
resultMap.remove(key);
76+
77+
resultMap.put(key+"List", data);
78+
}
79+
}
80+
*/
81+
82+
return resultMap;
83+
84+
85+
}
86+
87+
}

src/test/java/com/fasterxml/jackson/dataformat/xml/stream/XmlParserTest.java

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package com.fasterxml.jackson.dataformat.xml.stream;
22

3-
import java.io.*;
4-
5-
import com.fasterxml.jackson.core.*;
3+
import java.io.IOException;
4+
import java.io.StringReader;
5+
import java.io.StringWriter;
6+
7+
import com.fasterxml.jackson.core.JsonFactory;
8+
import com.fasterxml.jackson.core.JsonGenerator;
9+
import com.fasterxml.jackson.core.JsonParser;
10+
import com.fasterxml.jackson.core.JsonToken;
611
import com.fasterxml.jackson.databind.JsonNode;
712
import com.fasterxml.jackson.databind.ObjectMapper;
13+
import com.fasterxml.jackson.databind.ObjectWriter;
814
import com.fasterxml.jackson.dataformat.xml.XmlFactory;
915
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
1016
import com.fasterxml.jackson.dataformat.xml.XmlTestBase;
@@ -21,6 +27,7 @@ public class XmlParserTest extends XmlTestBase
2127
protected JsonFactory _jsonFactory;
2228
protected XmlFactory _xmlFactory;
2329
protected XmlMapper _xmlMapper;
30+
protected ObjectWriter _objectWriter;
2431

2532
// let's actually reuse XmlMapper to make things bit faster
2633
@Override
@@ -29,6 +36,7 @@ public void setUp() throws Exception {
2936
_jsonFactory = new JsonFactory();
3037
_xmlFactory = new XmlFactory();
3138
_xmlMapper = new XmlMapper();
39+
_objectWriter = new ObjectMapper().writer();
3240
}
3341

3442
/*
@@ -57,6 +65,13 @@ public void testSimpleNested() throws Exception
5765
_readXmlWriteJson("<root><a><b><c>xyz</c></b></a></root>"));
5866
}
5967

68+
public void testDuplicatedElementsSwallowing() throws Exception
69+
{
70+
// 04-Aug-2016, jpvarandas: ensure that duplicated elements get wrapped into an array and do not get swallowed (deser and ser)
71+
assertEquals("{\"name\":\"John\",\"parent\":[\"Jose\",\"Maria\"],\"dogs\":{\"count\":\"3\",\"dog\":[{\"name\":\"Spike\",\"age\":\"12\"},{\"name\":\"Brutus\",\"age\":\"9\"},{\"name\":\"Bob\",\"age\":\"14\"}]}}",
72+
_readXmlToMapToJson("<person><name>John</name><parent>Jose</parent><parent>Maria</parent><dogs><count>3</count><dog><name>Spike</name><age>12</age></dog><dog><name>Brutus</name><age>9</age></dog><dog><name>Bob</name><age>14</age></dog></dogs></person>"));
73+
}
74+
6075
/**
6176
* Unit test that verifies that we can write sample document from JSON
6277
* specification as XML, and read it back in "as JSON", with
@@ -270,4 +285,9 @@ private String _readXmlWriteJson(String xml) throws IOException
270285
jg.close();
271286
return w.toString();
272287
}
288+
289+
private String _readXmlToMapToJson(String xml) throws IOException
290+
{
291+
return _objectWriter.writeValueAsString(_xmlMapper.readValue(xml, Object.class));
292+
}
273293
}

0 commit comments

Comments
 (0)