Skip to content

Registering JaxbAnnotationModule causes custom PropertyNamingStrategy to be ignored #137

Open
@skwirking

Description

@skwirking

I am trying to use the JaxbAnnotationModule when serialising a jaxb object to string. I am trying to achieve two things:

  1. enums should be serialised with the jaxb enum value, rather than the java enum name
  2. 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);
      }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions