I am having some trouble with custom enum marshaling with Moxy and JSON. My use case is that I have a large object model that includes enumerations that normally should provide a normal enumerated value, a "code", and a description. The source of this data has only the "code", so I need to be able to unmarshal instances of these enums using only the code (e.g.
{"companyCode":{"code":"PI"}}
.
However, I should also be able to marshal and unmarshal all three fields:
{"companyCode":
{"value":"Private",
"code":"PI","description":
"Private Ins"
}
}
I am using an adapter that looks like this:
public class CodeEnumXmlAdapter<E extends Enum<E> & CodeEnum> extends XmlAdapter<CodeEnumImpl,E> {
public static <T extends Enum<T> & CodeEnum> T getFromName(Class<T> clazz, String name) {
if (name == null) return null;
T[] values = clazz.getEnumConstants();
for (T t : values) {
if (name.equals(t.name())) {
return t;
}
}
return null;
}
public static <T extends Enum<T> & CodeEnum> T getFromCode(Class<T> clazz, String code) {
if (code == null) return null;
T[] values = clazz.getEnumConstants();
for (T t : values) {
if (code.equals(t.getCode())) {
return t;
}
}
return null;
}
public static <T extends Enum<T> & CodeEnum> T getFromString(Class<T> clazz, String aString) {
if (aString == null) return null;
T[] values = clazz.getEnumConstants();
for (T t : values) {
if (aString.equals(t.getCode()) || aString.equals(t.name()) || aString.equals(t.getDescription())) {
return t;
}
}
return null;
}
@Override
public E unmarshal(CodeEnumImpl value) throws Exception {
if (value == null) return null;
String valueString = value.getValue();
if (valueString == null)
valueString = value.getCode();
if (valueString == null)
valueString = value.getDescription();
if (valueString == null)
return null;
Type generic = ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
return getFromString((Class<E>)generic, valueString);
}
@Override
public CodeEnumImpl marshal(E value) throws Exception {
return value == null ? null : new CodeEnumImpl(value);
}
}
This converts from a an enum like this:
import org.apache.commons.lang3.StringUtils;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlJavaTypeAdapter(CompanyCode.Adapter.class)
public enum CompanyCode implements CodeEnum {
// Changed "Commmercial" to "Client" based on inputs from ...Greg, Tamil
Client("CM", "Client"), Medicare("MC", "Medicare"), Medicaid("MD",
"Medicaid"), Private("PI", "Private Ins"), Patient("PT", "Patient");
private String code;
private String description;
private CompanyCode(String code, String label) {
this.code = code;
this.description = label;
}
public String getDescription() {
return description;
}
public String getCode() {
return code;
}
public static CompanyCode fromCode(String code) {
if (StringUtils.isEmpty(code)) {
return null;
}
for (CompanyCode freq : values()) {
if (freq.getCode().equalsIgnoreCase(code)) {
return freq;
}
}
throw new IllegalArgumentException("Invalid CompanyCode code: " + code);
}
public String toString() {
return description;
}
public static class Adapter extends CodeEnumXmlAdapter<CompanyCode> {}
}
and uses and intermediate type like this:
import javax.xml.bind.annotation.XmlElement;
/**
* Created by Jeffrey Hoffman on 6/24/2015.
*/
public class CodeEnumImpl {
String value;
String description;
String code;
public CodeEnumImpl() {
}
public <E extends Enum<E> & CodeEnum> CodeEnumImpl(E value) {
if (value != null) {
this.value = value.name();
this.description = value.getDescription();
this.code = value.getCode();
}
}
@XmlElement
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@XmlElement
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@XmlElement
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String toString() {
return value == null ? null : value.toString();
}
}
This is working fine with straight XML and JAXB. However, when I try to use Moxy, I get an exception like this:
Exception Description: The object [Private Ins], of class [class com.labcorp.phoenix.biz.enums.CompanyCode], could not be converted to [class java.lang.Object]. Internal Exception: Exception [EclipseLink-115] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DescriptorException Exception Description: No conversion value provided for the attribute [Private]. Mapping: org.eclipse.persistence.oxm.mappings.XMLDirectMapping[companyCode-->companyCode/text()] Descriptor: XMLDescriptor(com.labcorp.phoenix.eligibility.Root --> [DatabaseTable(root)]) at org.eclipse.persistence.exceptions.ConversionException.couldNotBeConverted(ConversionException.java:87) at org.eclipse.persistence.internal.jaxb.XMLJavaTypeConverter.convertObjectValueToDataValue(XMLJavaTypeConverter.java:178) at org.eclipse.persistence.oxm.mappings.XMLDirectMapping.convertObjectValueToDataValue(XMLDirectMapping.java:511) at org.eclipse.persistence.oxm.mappings.XMLDirectMapping.getFieldValue(XMLDirectMapping.java:330) at org.eclipse.persistence.internal.oxm.XMLDirectMappingNodeValue.marshalSingleValue(XMLDirectMappingNodeValue.java:62) at org.eclipse.persistence.internal.oxm.XMLDirectMappingNodeValue.marshal(XMLDirectMappingNodeValue.java:58) at org.eclipse.persistence.internal.oxm.NodeValue.marshal(NodeValue.java:102) at org.eclipse.persistence.internal.oxm.record.ObjectMarshalContext.marshal(ObjectMarshalContext.java:59) at org.eclipse.persistence.internal.oxm.XPathNode.marshal(XPathNode.java:393) at org.eclipse.persistence.internal.oxm.XPathNode.marshal(XPathNode.java:368) at org.eclipse.persistence.internal.oxm.XPathObjectBuilder.buildRow(XPathObjectBuilder.java:238) at org.eclipse.persistence.internal.oxm.TreeObjectBuilder.buildRow(TreeObjectBuilder.java:118) at org.eclipse.persistence.internal.oxm.TreeObjectBuilder.buildRow(TreeObjectBuilder.java:1) at org.eclipse.persistence.internal.oxm.XMLMarshaller.marshal(XMLMarshaller.java:743) at org.eclipse.persistence.internal.oxm.XMLMarshaller.marshal(XMLMarshaller.java:1124) at org.eclipse.persistence.internal.oxm.XMLMarshaller.marshal(XMLMarshaller.java:869) ... 7 more Caused by: Exception [EclipseLink-115] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DescriptorException
It seems like a bug in moxy, because my adapter converts to a non-enum type, so there should not be a nestedConverter that deals with enums.