Open
Description
I am trying to use the JaxbAnnotationModule when serialising a jaxb object to string. I am trying to achieve two things:
- enums should be serialised with the jaxb enum value, rather than the java enum name
- xml attributes names should be prefixed with the '@' symbol
I had hoped to use JaxbAnnotationModule to achieve 1. and a custom PropertyNamingStrategy to achieve 2. They work separately, but when I try to use both together, the custom PropertyNamingStrategy seems to never be invoked.
I don't know if my approach is wrong, or if this is a bug. Any help would be appreciated. Below is a pom.xml and junit 5 test that should reproduce the issue.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>jaxb-jackson</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package com.example.jaxbjackson;
import static org.junit.jupiter.api.Assertions.assertEquals;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
public class JaxbToJsonConverterTest {
@Test
public void usesCustomPropertyNamingStrategy() throws Exception {
AnXmlElement xmlelement = new AnXmlElement();
xmlelement.setAnXmlAttribute(AnXmlAttributesEnumValue.VALUE_ONE);
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new AtSymbolPropertyNamingStrategy());
String json = mapper.writeValueAsString(xmlelement);
String[] split = json.split(":");
assertEquals("{\"@AnXmlAttribute\"", split[0]);
}
@Test
public void usesJaxBAnnotationToDeriveEnumValue() throws Exception {
AnXmlElement xmlelement = new AnXmlElement();
xmlelement.setAnXmlAttribute(AnXmlAttributesEnumValue.VALUE_ONE);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JaxbAnnotationModule());
String json = mapper.writeValueAsString(xmlelement);
String[] split = json.split(":");
assertEquals("\"Value One\"}", split[1]);
}
@Test
public void usesJaxBAnnotationAndCustomPropertyNamingStrategyTogether() throws Exception {
AnXmlElement xmlelement = new AnXmlElement();
xmlelement.setAnXmlAttribute(AnXmlAttributesEnumValue.VALUE_ONE);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JaxbAnnotationModule());
mapper.setPropertyNamingStrategy(new AtSymbolPropertyNamingStrategy());
String json = mapper.writeValueAsString(xmlelement);
String[] split = json.split(":");
// below assertion fails because AtSymbolPropertyNamingStrategy is ignored
assertEquals("{\"@AnXmlAttribute\"", split[0]);
assertEquals("\"Value One\"}", split[1]);
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "")
@XmlRootElement(name = "AnXmlElement")
static class AnXmlElement {
@XmlAttribute(name = "AnXmlAttribute")
protected AnXmlAttributesEnumValue anXmlAttribute;
public AnXmlAttributesEnumValue getAnXmlAttribute() {
return anXmlAttribute;
}
public void setAnXmlAttribute(AnXmlAttributesEnumValue value) {
this.anXmlAttribute = value;
}
}
@XmlType(name = "anXmlAttributesEnumValues")
@XmlEnum
static enum AnXmlAttributesEnumValue {
@XmlEnumValue("Value One")
VALUE_ONE("Value One");
private final String value;
AnXmlAttributesEnumValue(String v) {
value = v;
}
public String value() {
return value;
}
public static AnXmlAttributesEnumValue fromValue(String v) {
for (AnXmlAttributesEnumValue c: AnXmlAttributesEnumValue.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
}
@SuppressWarnings("serial")
static class AtSymbolPropertyNamingStrategy extends PropertyNamingStrategy {
private String fieldName(AnnotatedMember member, String defaultName) {
XmlAttribute xmlAttributeAnnotation = member.getAllAnnotations().get(XmlAttribute.class);
if (xmlAttributeAnnotation != null) {
return "@" + xmlAttributeAnnotation.name();
}
return defaultName;
}
@Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
return fieldName(method, defaultName);
}
@Override
public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) {
return fieldName(field, defaultName);
}
@Override
public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
return fieldName(method, defaultName);
}
}
}