Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Registering JaxbAnnotationModule causes custom PropertyNamingStrategy to be ignored #137

Open
skwirking opened this issue Jun 15, 2021 · 4 comments

Comments

@skwirking
Copy link

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);
      }
    }
}
@cowtowncoder
Copy link
Member

Ah. I think I may know the issue here.

PropertyNamingStrategy is only applied to so-called "implicit" names: that is, names derived from getter/setter/field name. However, "explicit" names defined by things like annotations are not renamed by default: they are assumed to be exact names user wants, not subject to automatic renaming.

However, there is a setting:

MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING

enabling of which will change the behavior to apply naming strategy for such cases too.

So you may want to try that setting to see if it helps.

@skwirking
Copy link
Author

Thank you very much for the explanation and the suggested fix. That does indeed cause the tests that I included in the description to pass.

However, in the real code I'm working with I find there is still a problem when using the custom PropertyNamingStrategy with properties that are annotated with @XmlID.

I created the following test to reproduce the error:

Invalid Object Id definition for com.example.jaxbjackson.JaxbToJsonConverterTest$AnXmlElement: cannot find property with name 'AnXmlAttribute'

Is there a conflict here between processing JaxB annotations and renaming explicit properties? If so, can you suggest how I might resolve it?

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.XmlID;
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 usesJaxBAnnotationAndCustomPropertyNamingStrategyTogether() throws Exception {
      AnXmlElement xmlelement = new AnXmlElement();
      xmlelement.setAnXmlAttribute(AnXmlAttributesEnumValue.VALUE_ONE);

      ObjectMapper mapper = new ObjectMapper();
      mapper.registerModule(new JaxbAnnotationModule());
      mapper.setPropertyNamingStrategy(new AtSymbolPropertyNamingStrategy());
      mapper.configure(MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING, true);

      String json = mapper.writeValueAsString(xmlelement);
      
      String[] split = json.split(":");

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

@skwirking
Copy link
Author

@cowtowncoder any suggestion on how to get past this issue with @XmlID?

@cowtowncoder
Copy link
Member

@skwirking No. You may try mailing lists; I don't have time to dig into this at this point in time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants