Search code examples
jaxbapache-cameljaxb2jackson2jaxb2-maven-plugin

ObjectMapper throws exception: "no single-String constructor/factory method", due to "nillable" attribute? What can be done?


I need help to solve/fix the cause of the following exception...

i.e.,

Can not instantiate value of type [simple type, class javax.xml.bind.JAXBElement<javax.xml.datatype.XMLGregorianCalendar>] 
from String value ('2018-02-04'); 
no single-String constructor/factory method

This is the json string in question

i.e.,

{"MySubGroup":[{"MyDateTime":"2018-02-01T01:01:01.001-05:00","MyDate":"2018-02-04T01:01:01.001-05:00"}]}

The schema element that is causing the parsing to fail looks like this...

i.e.,

<xs:element name="MyDate" type="xs:date" minOccurs="0" nillable="true"/>

(PLEASE NOTE: if I remove "nillable" attribute from MyDate schema element, the example runs without issues)

QUESTION:

--How can I overcome this issue *without* removing this "nillable" attribute?

This is the schema...

i.e.,

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema 
    targetNamespace="http://aaa.bbb.ccc"
    attributeFormDefault="unqualified"
    elementFormDefault="qualified"
    xmlns:es="http://aaa.bbb.ccc"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">    
    <xs:complexType name="MySubGroupType">
    <xs:all>
        <xs:element name="MyDateTime" type="xs:dateTime" minOccurs="0" />
        <xs:element name="MyDate" type="xs:date" minOccurs="0" nillable="true"/>                                        
    </xs:all>
    </xs:complexType>
    <xs:element name="MyGroup">
    <xs:complexType>
        <xs:sequence>
        <xs:element name="MySubGroup" type="es:MySubGroupType" minOccurs="0" maxOccurs="unbounded" />
        </xs:sequence>
    </xs:complexType>
    </xs:element>
</xs:schema>

This is the code in question...

i.e.,

String jsonString = "{\"MySubGroup\":[{\"MyDateTime\":\"2018-02-01T01:01:01.001-05:00\",\"MyDate\":\"2018-02-04\"}]}";
om = new ObjectMapper();
om.findAndRegisterModules();
om.setPropertyNamingStrategy(new PreservePropertyNameStrategy()); // (NOTE: PropertyNamingStrategy.UPPER_CASE is not available in jackson 2.6.3)
mg = om.readValue(jsonString, MyGroup.class);
MySubGroupType sgt = mg.getMySubGroup().get(0);
System.out.println("value of MyDate element is:" +  sgt.getMyDate());   

The Exception occurs on this statement...

i.e.,

mg = om.readValue(jsonString, MyGroup.class);

More details below, if needed...


MyRoute.java...

i.e,

package aaa.bbb.ccc.jar;

import aaa.bbb.ccc.generated.MyGroup;
import aaa.bbb.ccc.generated.MySubGroupType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.cdi.ContextName;

@ContextName("rest-dsl")
public class MyRoutes extends RouteBuilder {

    public MyRoutes() {
    }

    private ObjectMapper om = new ObjectMapper();
    private MyGroup mg;

    @Override
    public void configure() throws Exception {

    org.apache.log4j.MDC.put("app.name", "testjsonparse");

    from("timer://foo?fixedRate=true&repeatCount=1")
        .setBody(constant("placeholder"))
        .to("seda:node1");

    from("seda:node1")
        .process(new Processor() {
            @Override
            public void process(Exchange exchange) {
            try {
                String jsonString = "{\"MySubGroup\":[{\"MyDateTime\":\"2018-02-01T01:01:01.001-05:00\",\"MyDate\":\"2018-02-04\"}]}";
                om = new ObjectMapper();
                om.findAndRegisterModules();
                om.setPropertyNamingStrategy(new PreservePropertyNameStrategy()); //not available in jackson 2.6.3:  PropertyNamingStrategy.UPPER_CASE);
                mg = om.readValue(jsonString, MyGroup.class);
                MySubGroupType sgt = mg.getMySubGroup().get(0);
                exchange.getIn().setBody("+++success+++");
            } catch (Exception e) {
                e.printStackTrace();
                exchange.getIn().setBody("---failure---");
            }
            }
        })
        .to("seda:node2");

        from("seda:node2")  
        .log("\n\n... ${body}");
    }
}

PreservePropertyNameStrategy.java...

i.e.,

package aaa.bbb.ccc.jar;

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.AnnotatedMethod;

// the myschema.xml schema (for academic reasons) capitalizes the 1st letter of each property name...
//...we handle the issue by extending/customizing PropertyNamingStrategy - mainly because the option "PropertyNamingStrategy.UPPER_CASE" is not yet available in jackson 2.6.3 (?)...
public class PreservePropertyNameStrategy extends PropertyNamingStrategy {
    @Override
    public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) {
    return Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1);
    }

    @Override
    public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
    String m = method.getName();   
    return (m.startsWith("is")?m.substring(2):m.substring(3));
    }

    @Override
    public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
    return method.getName().substring(3);        
    }

}

MySubGroupType.java - generated from the schema...

i.e.,

package aaa.bbb.ccc.generated;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlType;
import javax.xml.datatype.XMLGregorianCalendar;


/**
 * <p>Java class for MySubGroupType complex type.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * 
 * <pre>
 * &lt;complexType name="MySubGroupType"&gt;
 *   &lt;complexContent&gt;
 *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
 *       &lt;all&gt;
 *         &lt;element name="MyDateTime" type="{http://www.w3.org/2001/XMLSchema}dateTime" minOccurs="0"/&gt;
 *         &lt;element name="MyDate" type="{http://www.w3.org/2001/XMLSchema}date" minOccurs="0"/&gt;
 *       &lt;/all&gt;
 *     &lt;/restriction&gt;
 *   &lt;/complexContent&gt;
 * &lt;/complexType&gt;
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "MySubGroupType", propOrder = {

})
public class MySubGroupType {

    @XmlElement(name = "MyDateTime")
    @XmlSchemaType(name = "dateTime")
    protected XMLGregorianCalendar myDateTime;
    @XmlElementRef(name = "MyDate", namespace = "http://aaa.bbb.ccc", type = JAXBElement.class, required = false)
    protected JAXBElement<XMLGregorianCalendar> myDate;

    /**
     * Gets the value of the myDateTime property.
     * 
     * @return
     *     possible object is
     *     {@link XMLGregorianCalendar }
     *     
     */
    public XMLGregorianCalendar getMyDateTime() {
    return myDateTime;
    }

    /**
     * Sets the value of the myDateTime property.
     * 
     * @param value
     *     allowed object is
     *     {@link XMLGregorianCalendar }
     *     
     */
    public void setMyDateTime(XMLGregorianCalendar value) {
    this.myDateTime = value;
    }

    /**
     * Gets the value of the myDate property.
     * 
     * @return
     *     possible object is
     *     {@link JAXBElement }{@code <}{@link XMLGregorianCalendar }{@code >}
     *     
     */
    public JAXBElement<XMLGregorianCalendar> getMyDate() {
    return myDate;
    }

    /**
     * Sets the value of the myDate property.
     * 
     * @param value
     *     allowed object is
     *     {@link JAXBElement }{@code <}{@link XMLGregorianCalendar }{@code >}
     *     
     */
    public void setMyDate(JAXBElement<XMLGregorianCalendar> value) {
    this.myDate = value;
    }

}

MyGroup.java - generated from the schema...

i.e.,

//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.11 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2018.02.08 at 05:44:17 PM EST 
//


package aaa.bbb.ccc.generated;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;


/**
 * <p>Java class for anonymous complex type.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * 
 * <pre>
 * &lt;complexType&gt;
 *   &lt;complexContent&gt;
 *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
 *       &lt;sequence&gt;
 *         &lt;element name="MySubGroup" type="{http://aaa.bbb.ccc}MySubGroupType" maxOccurs="unbounded" minOccurs="0"/&gt;
 *       &lt;/sequence&gt;
 *     &lt;/restriction&gt;
 *   &lt;/complexContent&gt;
 * &lt;/complexType&gt;
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "mySubGroup"
})
@XmlRootElement(name = "MyGroup")
public class MyGroup {

    @XmlElement(name = "MySubGroup")
    protected List<MySubGroupType> mySubGroup;

    /**
     * Gets the value of the mySubGroup property.
     * 
     * <p>
     * This accessor method returns a reference to the live list,
     * not a snapshot. Therefore any modification you make to the
     * returned list will be present inside the JAXB object.
     * This is why there is not a <CODE>set</CODE> method for the mySubGroup property.
     * 
     * <p>
     * For example, to add a new item, do as follows:
     * <pre>
     *    getMySubGroup().add(newItem);
     * </pre>
     * 
     * 
     * <p>
     * Objects of the following type(s) are allowed in the list
     * {@link MySubGroupType }
     * 
     * 
     */
    public List<MySubGroupType> getMySubGroup() {
    if (mySubGroup == null) {
        mySubGroup = new ArrayList<MySubGroupType>();
    }
    return this.mySubGroup;
    }

}

This is the pom.xml...

i.e.,

<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>aaa.bbb.ccc</groupId>
    <artifactId>testjsonissue</artifactId>
    <version>1.0.0</version>
    <packaging>bundle</packaging>
    <name>testjsonissue</name>
    <description>testjsonissue</description>

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <skipTests>true</skipTests>
    <mq.version>8.0.0.7</mq.version>
    <timestamp>v${maven.build.timestamp}</timestamp>        
    <maven.build.timestamp.format>yyyyMMdd.HHmmss</maven.build.timestamp.format>        
    </properties>

    <dependencies>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.25</version>
        <scope>provided</scope>               
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.25</version>
        <scope>provided</scope>               
    </dependency>
    <dependency>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-core</artifactId>
        <version>2.17.0</version>
        <scope>provided</scope>
    </dependency>     
    <dependency>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-cdi</artifactId>
        <version>2.17.0</version> 
        <scope>provided</scope>                     
    </dependency>    
    <dependency>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-jackson</artifactId>
        <version>2.17.0</version>
        <scope>provided</scope>               
    </dependency>             
    <!-- version to use, as per: https://mvnrepository.com/artifact/org.apache.camel/camel-jackson/2.17.0.redhat-630283  -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.6.3</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
        <version>2.6.3</version>
    </dependency>
    </dependencies>

    <build>
    <finalName>${project.artifactId}-${project.version}</finalName>
    <resources>
        <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
        </resource>
    </resources>
    <plugins>
        <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
            <showDeprecation>true</showDeprecation>
        </configuration>
        </plugin> 
        <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>jaxb2-maven-plugin</artifactId>
        <version>2.3</version>
        <executions>
            <execution>
            <id>xjc</id>
            <goals>
                <goal>xjc</goal>
            </goals>
            </execution>
        </executions>        
        <configuration>
            <sources>
            <source>src/main/resources/xsd/myschema.xsd</source>
            </sources>
            <packageName>aaa.bbb.ccc.generated</packageName>                    
            <verbose default-value="false">${xjc.verbose}</verbose>
        </configuration>                
        </plugin>              
        <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>3.3.0</version>
        <extensions>true</extensions>
        <configuration>
            <instructions>
            <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
            <Export-Package>aaa.bbb.ccc*</Export-Package>    
            <Bundle-Version>${project.version}-${timestamp}</Bundle-Version>                                                                                            
            </instructions>
        </configuration>            
        </plugin>            
    </plugins>
    </build>
</project>

**Environment... I'm confined to these dependencies/versions, etc **

i.e.,

Java 8
Jboss Fuse, v6.3.0
camel-jackson, v2.17.0
jackson-databind, v2.6.3
jackson-datatype-jsr310, v2.6.3

Solution

  • Apparently, was missing the following instruction...

    om.registerModule(new JavaTimeModule());     
    

    ...now works...

    in otherwords, this

                String jsonString = "{\"MySubGroup\":[{\"MyDateTime\":\"2018-02-01T01:01:01.001-05:00\",\"MyDate\":\"2018-02-04\"}]}";
                om = new ObjectMapper();
                om.findAndRegisterModules();
                om.setPropertyNamingStrategy(new PreservePropertyNameStrategy()); //not available in jackson 2.6.3:  PropertyNamingStrategy.UPPER_CASE);
                mg = om.readValue(jsonString, MyGroup.class);
                MySubGroupType sgt = mg.getMySubGroup().get(0);
                exchange.getIn().setBody("+++success+++");
    

    ...is changed to this...

                String jsonString = "{\"MySubGroup\":[{\"MyDateTime\":\"2018-02-01T01:01:01.001-05:00\",\"MyDate\":\"2018-02-04\"}]}";
                om = new ObjectMapper();
                om.registerModule(new JavaTimeModule()); //<=== changed
                om.setPropertyNamingStrategy(new PreservePropertyNameStrategy()); //not available in jackson 2.6.3:  PropertyNamingStrategy.UPPER_CASE);
                mg = om.readValue(jsonString, MyGroup.class);
                MySubGroupType sgt = mg.getMySubGroup().get(0);
                exchange.getIn().setBody("+++success+++");