Skip to content

Commit

Permalink
Add feature for QNAME serialization and deserialization.
Browse files Browse the repository at this point in the history
This commit fixes FasterXML#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 FasterXML#4771).
  • Loading branch information
mcvayc authored and mcvayc committed Jan 2, 2025
1 parent f624751 commit d7476c6
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,19 @@ public enum DeserializationFeature implements ConfigFeature
*/
READ_ENUMS_USING_TO_STRING(false),

/**
* Feature that determines standard deserialization mechanism used for
* QName values: if enabled, QNames are assumed to have been serialized using
* return value of <code>QName.toString()</code>;
* if disabled, it is assumed that the QName was serialized as an object.
*<p>
* Note: this feature should usually have same value
* as {@link SerializationFeature#WRITE_QNAMES_USING_TO_STRING}.
*<p>
* Feature is disabled by default.
*/
READ_QNAMES_USING_VALUE_OF(true),

/**
* Feature that allows unknown Enum values to be parsed as {@code null} values.
* If disabled, unknown Enum values will throw exceptions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,18 @@ public enum SerializationFeature implements ConfigFeature
*/
WRITE_ENUMS_USING_TO_STRING(false),

/**
* Feature that determines standard serialization mechanism used for
* QName values: if enabled, return value of <code>QName.toString()</code>
* is used; if disabled, the QName is serialized as an object.
*<p>
* Note: this feature should usually have same value
* as {@link DeserializationFeature#READ_QNAMES_USING_VALUE_OF}.
*<p>
* Feature is disabled by default.
*/
WRITE_QNAMES_USING_TO_STRING(true),

/**
* Feature that determines whether Java Enum values are serialized
* as numbers (true), or textual values (false). If textual values are
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import javax.xml.namespace.QName;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.TypeReference;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.Deserializers;
import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer;
Expand Down Expand Up @@ -40,7 +42,10 @@ public JsonDeserializer<?> findBeanDeserializer(JavaType type,
{
Class<?> raw = type.getRawClass();
if (raw == QName.class) {
return new Std(raw, TYPE_QNAME);
if (config == null || config.isEnabled(DeserializationFeature.READ_QNAMES_USING_VALUE_OF)) {
return new Std(raw, TYPE_QNAME);
}
return new QNameObjectDeserializer();
}
if (raw == XMLGregorianCalendar.class) {
return new Std(raw, TYPE_G_CALENDAR);
Expand Down Expand Up @@ -149,4 +154,27 @@ protected XMLGregorianCalendar _gregorianFromDate(DeserializationContext ctxt,
return _dataTypeFactory.newXMLGregorianCalendar(calendar);
}
}

private class QNameObjectDeserializer extends JsonDeserializer<QName> {
private static final long serialVersionUID = 1L;
public static final TypeReference<Map<String, String>> STRING_MAP_TYPE_REFERENCE = new TypeReference<>() {};

@Override
public QName deserialize(final JsonParser p, final DeserializationContext ctxt)
throws IOException
{
Map<String, String> map;
try {
map = p.readValueAs(STRING_MAP_TYPE_REFERENCE);
} catch (IOException e) {
throw new JsonMappingException(p, "Unable to parse the QName as an object.", e);
}

return new QName(
map.getOrDefault("namespaceURI", ""),
map.getOrDefault("localPart", ""),
map.getOrDefault("prefix", "")
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,15 @@ public JsonSerializer<?> findSerializer(SerializationConfig config,
JavaType type, BeanDescription beanDesc)
{
Class<?> raw = type.getRawClass();
if (Duration.class.isAssignableFrom(raw) || QName.class.isAssignableFrom(raw)) {
if (Duration.class.isAssignableFrom(raw)){
return ToStringSerializer.instance;
}
if (QName.class.isAssignableFrom(raw)) {
if (config.isEnabled(SerializationFeature.WRITE_QNAMES_USING_TO_STRING)) {
return ToStringSerializer.instance;
}
return null;
}
if (XMLGregorianCalendar.class.isAssignableFrom(raw)) {
return XMLGregorianCalendarSerializer.instance;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.fasterxml.jackson.databind.ext;

import java.util.stream.Stream;
import javax.xml.namespace.QName;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import com.fasterxml.jackson.core.JsonProcessingException;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import static org.junit.jupiter.api.Assertions.assertEquals;

import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.newJsonMapper;

class QNameAsObjectReadWrite4771Test {

private final ObjectMapper MAPPER = newJsonMapper();

@ParameterizedTest
@MethodSource("provideAllPerumtationsOfQNameConstructor")
void testQNameWithObjectSerialization(QName originalQName) throws JsonProcessingException {
String json = MAPPER
.disable(SerializationFeature.WRITE_QNAMES_USING_TO_STRING)
.writeValueAsString(originalQName);

QName deserializedQName = MAPPER
.disable(DeserializationFeature.READ_QNAMES_USING_VALUE_OF)
.readValue(json, QName.class);

assertEquals(originalQName, deserializedQName);
}

static Stream<Arguments> provideAllPerumtationsOfQNameConstructor() {
return Stream.of(
Arguments.of(new QName("test-local-part")),
Arguments.of(new QName("test-namespace-uri", "test-local-part")),
Arguments.of(new QName("test-namespace-uri", "test-local-part", "test-prefix"))
);
}

}

0 comments on commit d7476c6

Please sign in to comment.