Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,50 @@ public Std(Class<?> raw, int kind) {
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
// For most types, use super impl; but GregorianCalendar also allows
// integer value (timestamp), which needs separate handling
// GregorianCalendar also allows integer value (timestamp),
// which needs separate handling
if (_kind == TYPE_G_CALENDAR) {
if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
return _gregorianFromDate(ctxt, _parseDate(p, ctxt));
}
}
// QName also allows object value, which needs separate handling
if (_kind == TYPE_QNAME) {
if (p.hasToken(JsonToken.START_OBJECT)) {
return _parseQNameObject(p, ctxt);
}
}
return super.deserialize(p, ctxt);
}

private QName _parseQNameObject(JsonParser p, DeserializationContext ctxt)
throws IOException
{
JsonNode tree = ctxt.readTree(p);

if (!tree.has("localPart")) {
throw new JsonParseException("QName is missing required field: localPart");
}

JsonNode localPart = tree.get("localPart");
if (!localPart.isTextual()) {
throw new JsonParseException("QName field \"localPart\" must be of type String.");
}

if (tree.has("namespaceURI")) {
JsonNode namespaceURI = tree.get("namespaceURI");

if (tree.has("prefix")) {
JsonNode prefix = tree.get("prefix");
return new QName(namespaceURI.asText(), localPart.asText(), prefix.asText());
}

return new QName(namespaceURI.asText(), localPart.asText());
} else {
return new QName(localPart.asText());
}
}

@Override
protected Object _deserialize(String value, DeserializationContext ctxt)
throws IOException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
Expand Down Expand Up @@ -34,9 +35,12 @@ 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)) {
return QNameSerializer.instance;
}
if (XMLGregorianCalendar.class.isAssignableFrom(raw)) {
return XMLGregorianCalendarSerializer.instance;
}
Expand Down Expand Up @@ -116,4 +120,55 @@ protected Calendar _convert(XMLGregorianCalendar input) {
return (input == null) ? null : input.toGregorianCalendar();
}
}

public static class QNameSerializer
extends StdSerializer<QName>
implements ContextualSerializer
{
private static final long serialVersionUID = 1L;

public static JsonSerializer<?> instance = new QNameSerializer();

public QNameSerializer() {
super(QName.class);
}

@Override
public JsonSerializer<?> createContextual(SerializerProvider serializers, BeanProperty property)
throws JsonMappingException
{
JsonFormat.Value format = findFormatOverrides(serializers, property, handledType());
if (format != null) {
JsonFormat.Shape shape = format.getShape();
if (shape == JsonFormat.Shape.OBJECT) {
return this;
}
}
return ToStringSerializer.instance;
}

@Override
public void serialize(QName value, JsonGenerator g, SerializerProvider provider)
throws IOException
{
g.writeStartObject();
g.writeObjectField("localPart", value.getLocalPart());
if(!value.getNamespaceURI().isEmpty()) g.writeObjectField("namespaceURI", value.getNamespaceURI());
if(!value.getPrefix().isEmpty()) g.writeObjectField("prefix", value.getPrefix());
g.writeEndObject();
}

@Override
public final void serializeWithType(QName value, JsonGenerator g, SerializerProvider provider,
TypeSerializer typeSer)
throws IOException
{
g.writeObject(value);
}

@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
visitor.expectBooleanFormat(typeHint);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not boolean, object, will change.

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import javax.xml.namespace.QName;
import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import com.fasterxml.jackson.databind.testutil.NoCheckSubTypeValidator;
Expand Down Expand Up @@ -40,6 +41,18 @@ public void testQNameSer() throws Exception
assertEquals(q(qn.toString()), MAPPER.writeValueAsString(qn));
}

@Test
public void testQNameSerToObject() throws Exception
{
QName qn = new QName("http://abc", "tag", "prefix");

ObjectMapper mapper = jsonMapperBuilder()
.withConfigOverride(QName.class, cfg -> cfg.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.OBJECT)))
.build();

assertEquals(a2q("{'localPart':'tag','namespaceURI':'http://abc','prefix':'prefix'}"), mapper.writeValueAsString(qn));
}

@Test
public void testDurationSer() throws Exception
{
Expand Down Expand Up @@ -121,6 +134,21 @@ public void testQNameDeser() throws Exception
assertEquals("", qn.getLocalPart());
}

@Test
public void testQNameDeserFromObject() throws Exception
{
String qstr = a2q("{'namespaceURI':'http://abc','localPart':'tag','prefix':'prefix'}");
ObjectMapper mapper = jsonMapperBuilder()
.withConfigOverride(QName.class, cfg -> cfg.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.OBJECT)))
.build();

QName qn = mapper.readValue(qstr, QName.class);

assertEquals("http://abc", qn.getNamespaceURI());
assertEquals("tag", qn.getLocalPart());
assertEquals("prefix", qn.getPrefix());
}

@Test
public void testXMLGregorianCalendarDeser() throws Exception
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.fasterxml.jackson.databind.ext;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

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

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

class QNameAsObjectReadWrite4771Test extends DatabindTestUtil
{

private final ObjectMapper MAPPER = newJsonMapper();

static class BeanWithQName {
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public QName qname;

public BeanWithQName() {
}

public BeanWithQName(QName qName) {
this.qname = qName;
}
}


@ParameterizedTest
@MethodSource("provideAllPerumtationsOfQNameConstructor")
void testQNameWithObjectSerialization(QName originalQName) throws JsonProcessingException
{
BeanWithQName bean = new BeanWithQName(originalQName);

String json = MAPPER.writeValueAsString(bean);

QName deserializedQName = MAPPER.readValue(json, BeanWithQName.class).qname;

assertEquals(originalQName.getLocalPart(), deserializedQName.getLocalPart());
assertEquals(originalQName.getNamespaceURI(), deserializedQName.getNamespaceURI());
assertEquals(originalQName.getPrefix(), deserializedQName.getPrefix());
}

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"))
);
}

}