Skip to content

Commit 90d40e0

Browse files
mcvaycmcvayc
mcvayc
authored andcommitted
Add feature for QNAME serialization and deserialization.
This commit fixes #4771 by adding serialization and deserialization features to control whether a QName is serialized to a string using the "QName.toString()" method (the only option currently) or if it is serialized to JSON object (a new option to fix #4771).
1 parent bc66348 commit 90d40e0

File tree

6 files changed

+124
-2
lines changed

6 files changed

+124
-2
lines changed

src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java

+13
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,19 @@ public enum DeserializationFeature implements ConfigFeature
431431
*/
432432
READ_ENUMS_USING_TO_STRING(false),
433433

434+
/**
435+
* Feature that determines standard deserialization mechanism used for
436+
* QName values: if enabled, QNames are assumed to have been serialized using
437+
* return value of <code>QName.toString()</code>;
438+
* if disabled, it is assumed that the QName was serialized as an object.
439+
*<p>
440+
* Note: this feature should usually have same value
441+
* as {@link SerializationFeature#WRITE_QNAMES_USING_TO_STRING}.
442+
*<p>
443+
* Feature is disabled by default.
444+
*/
445+
READ_QNAMES_USING_VALUE_OF(true),
446+
434447
/**
435448
* Feature that allows unknown Enum values to be parsed as {@code null} values.
436449
* If disabled, unknown Enum values will throw exceptions.

src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java

+12
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,18 @@ public enum SerializationFeature implements ConfigFeature
292292
*/
293293
WRITE_ENUMS_USING_TO_STRING(false),
294294

295+
/**
296+
* Feature that determines standard serialization mechanism used for
297+
* QName values: if enabled, return value of <code>QName.toString()</code>
298+
* is used; if disabled, the QName is serialized as an object.
299+
*<p>
300+
* Note: this feature should usually have same value
301+
* as {@link DeserializationFeature#READ_QNAMES_USING_VALUE_OF}.
302+
*<p>
303+
* Feature is disabled by default.
304+
*/
305+
WRITE_QNAMES_USING_TO_STRING(true),
306+
295307
/**
296308
* Feature that determines whether Java Enum values are serialized
297309
* as numbers (true), or textual values (false). If textual values are

src/main/java/com/fasterxml/jackson/databind/ext/CoreXMLDeserializers.java

+29-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import javax.xml.namespace.QName;
88

99
import com.fasterxml.jackson.core.*;
10+
import com.fasterxml.jackson.core.type.TypeReference;
11+
1012
import com.fasterxml.jackson.databind.*;
1113
import com.fasterxml.jackson.databind.deser.Deserializers;
1214
import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer;
@@ -40,7 +42,10 @@ public JsonDeserializer<?> findBeanDeserializer(JavaType type,
4042
{
4143
Class<?> raw = type.getRawClass();
4244
if (raw == QName.class) {
43-
return new Std(raw, TYPE_QNAME);
45+
if (config == null || config.isEnabled(DeserializationFeature.READ_QNAMES_USING_VALUE_OF)) {
46+
return new Std(raw, TYPE_QNAME);
47+
}
48+
return new QNameObjectDeserializer();
4449
}
4550
if (raw == XMLGregorianCalendar.class) {
4651
return new Std(raw, TYPE_G_CALENDAR);
@@ -149,4 +154,27 @@ protected XMLGregorianCalendar _gregorianFromDate(DeserializationContext ctxt,
149154
return _dataTypeFactory.newXMLGregorianCalendar(calendar);
150155
}
151156
}
157+
158+
private class QNameObjectDeserializer extends JsonDeserializer<QName> {
159+
private static final long serialVersionUID = 1L;
160+
public static final TypeReference<Map<String, String>> STRING_MAP_TYPE_REFERENCE = new TypeReference<>() {};
161+
162+
@Override
163+
public QName deserialize(final JsonParser p, final DeserializationContext ctxt)
164+
throws IOException
165+
{
166+
Map<String, String> map;
167+
try {
168+
map = p.readValueAs(STRING_MAP_TYPE_REFERENCE);
169+
} catch (IOException e) {
170+
throw new JsonMappingException(p, "Unable to parse the QName as an object.", e);
171+
}
172+
173+
return new QName(
174+
map.getOrDefault("namespaceURI", ""),
175+
map.getOrDefault("localPart", ""),
176+
map.getOrDefault("prefix", "")
177+
);
178+
}
179+
}
152180
}

src/main/java/com/fasterxml/jackson/databind/ext/CoreXMLSerializers.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,15 @@ public JsonSerializer<?> findSerializer(SerializationConfig config,
3434
JavaType type, BeanDescription beanDesc)
3535
{
3636
Class<?> raw = type.getRawClass();
37-
if (Duration.class.isAssignableFrom(raw) || QName.class.isAssignableFrom(raw)) {
37+
if (Duration.class.isAssignableFrom(raw)){
3838
return ToStringSerializer.instance;
3939
}
40+
if (QName.class.isAssignableFrom(raw)) {
41+
if (config.isEnabled(SerializationFeature.WRITE_QNAMES_USING_TO_STRING)) {
42+
return ToStringSerializer.instance;
43+
}
44+
return null;
45+
}
4046
if (XMLGregorianCalendar.class.isAssignableFrom(raw)) {
4147
return XMLGregorianCalendarSerializer.instance;
4248
}

src/test/java/com/fasterxml/jackson/databind/ext/MiscJavaXMLTypesReadWriteTest.java

+17
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ public void testQNameSer() throws Exception
4040
assertEquals(q(qn.toString()), MAPPER.writeValueAsString(qn));
4141
}
4242

43+
@Test
44+
public void testQNameSerWithToStringFeatureDisabled() throws Exception
45+
{
46+
QName qn = new QName("http://abc", "tag", "prefix");
47+
assertEquals(a2q("{'namespaceURI':'http://abc','localPart':'tag','prefix':'prefix'}"), MAPPER.disable(SerializationFeature.WRITE_QNAMES_USING_TO_STRING).writeValueAsString(qn));
48+
}
49+
4350
@Test
4451
public void testDurationSer() throws Exception
4552
{
@@ -121,6 +128,16 @@ public void testQNameDeser() throws Exception
121128
assertEquals("", qn.getLocalPart());
122129
}
123130

131+
@Test
132+
public void testQNameDeserWithValueOfFeatureDisabled() throws Exception
133+
{
134+
String qstr = a2q("{'namespaceURI':'http://abc','localPart':'tag','prefix':'prefix'}");
135+
QName qn = MAPPER.disable(DeserializationFeature.READ_QNAMES_USING_VALUE_OF).readValue(qstr, QName.class);
136+
assertEquals("http://abc", qn.getNamespaceURI());
137+
assertEquals("tag", qn.getLocalPart());
138+
assertEquals("prefix", qn.getPrefix());
139+
}
140+
124141
@Test
125142
public void testXMLGregorianCalendarDeser() throws Exception
126143
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.fasterxml.jackson.databind.ext;
2+
3+
import java.util.stream.Stream;
4+
import javax.xml.namespace.QName;
5+
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.Arguments;
8+
import org.junit.jupiter.params.provider.MethodSource;
9+
10+
import com.fasterxml.jackson.core.JsonProcessingException;
11+
12+
import com.fasterxml.jackson.databind.DeserializationFeature;
13+
import com.fasterxml.jackson.databind.ObjectMapper;
14+
import com.fasterxml.jackson.databind.SerializationFeature;
15+
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
18+
import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.newJsonMapper;
19+
20+
class QNameAsObjectReadWrite4771Test {
21+
22+
private final ObjectMapper MAPPER = newJsonMapper();
23+
24+
@ParameterizedTest
25+
@MethodSource("provideAllPerumtationsOfQNameConstructor")
26+
void testQNameWithObjectSerialization(QName originalQName) throws JsonProcessingException {
27+
String json = MAPPER
28+
.disable(SerializationFeature.WRITE_QNAMES_USING_TO_STRING)
29+
.writeValueAsString(originalQName);
30+
31+
QName deserializedQName = MAPPER
32+
.disable(DeserializationFeature.READ_QNAMES_USING_VALUE_OF)
33+
.readValue(json, QName.class);
34+
35+
assertEquals(originalQName, deserializedQName);
36+
}
37+
38+
static Stream<Arguments> provideAllPerumtationsOfQNameConstructor() {
39+
return Stream.of(
40+
Arguments.of(new QName("test-local-part")),
41+
Arguments.of(new QName("test-namespace-uri", "test-local-part")),
42+
Arguments.of(new QName("test-namespace-uri", "test-local-part", "test-prefix"))
43+
);
44+
}
45+
46+
}

0 commit comments

Comments
 (0)