Предоставление сериализации пользовательских значений для перечислений через JAXB
Для проекта, над которым я работаю, у нас много перечислений. Сам объект модели состоит из множества крошечных классов; эту модель мы затем сериализуем в нашу БД как XML через JAXB. Теперь мы хотим иметь возможность сериализовать наши значения enum, используя возврат определенного метода в перечисление; который указан:
public enum Qualifier {
FOO("1E", "Foo type document"),
BAR("2", "Bar object");
private String code, description;
public Qualifier(String code, String description) {
this.code = code;
this.description = description;
}
public String getCode() {
return this.code;
}
public String getDescription() {
return this.description;
}
}
и т.д.. и т.д. В настоящее время, когда сериализуется в XML, мы получаем что-то вроде:
<qualifier>FOO</qualifier>
как это делает JAXB. Однако нам нужно, чтобы значение возвращалось getCode(), и многие наши перечисления следуют этому соглашению (с соответствующим статическим методом для поиска через код), так что приведенный выше фрагмент XML выглядит следующим образом:
<qualifier>1E</qualifier>
вместо этого. Мы можем аннотировать его с помощью @XmlEnum
и @XmlEnumValue
, но это слишком утомительно - некоторые перечисления имеют до 30 перечислимых значений, а редактирование вручную это не очень хорошо. Мы также думаем использовать пользовательский сериализатор вместо этого, но я бы хотел избежать этого маршрута на данный момент (но если это так, то у меня нет проблем с ним).
Любые идеи как?
Ответы
Ответ 1
Попробуйте использовать механизм XmlAdapter
для этого. Вы создаете подкласс XmlAdapter
для каждого типа перечисления и который знает, как маршал /unmarshal перечисление в и из XML.
Затем вы связываете адаптер с этим свойством, например
public class QualifierAdapter extends XmlAdapter<String, Qualifier> {
public String marshal(Qualifier qualifier) {
return qualifier.getCode();
}
public Qualifier unmarshal(String val) {
return Qualifier.getFromCode(val); // I assume you have a way of doing this
}
}
а затем в классах модели:
@XmlJavaTypeAdapter(QualifierAdapter.class)
private Qualifier qualifier;
Вы также можете объявить это на уровне пакета внутри файла с именем package-info.java
в том же пакете, что и ваши классы моделей, используя довольно своеобразные аннотации пакетов:
@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters({
@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(
type=Qualifier.class, value=QualifierAdapter.class
)
})
package com.xyz;
Ответ 2
Нашел этот вопрос, ища что-то еще, но я прочитал ваш комментарий о чем-то более общем. Heres, что я использовал, чтобы преобразовать типы перечислений верхнего регистра в случай верблюда. Я использую ваш тип enum
, но поставлю на него свой адаптер. Как вы можете видеть, вам не нужно ссылаться на каждый экземпляр Qualifier, но просто аннотировать сам перечисление.
CamelCaseEnumAdapter может принимать любой enum
, однако класс enum
должен быть передан ему, поэтому вам нужно, чтобы класс расширил его, я просто использую закрытый статический класс внутри самого перечисления.
Enum:
@XmlJavaTypeAdapter(Qualifier.Adapter.class)
public enum Qualifier {
FOO("1E", "Foo type document"),
BAR("2", "Bar object");
private String code, description;
public Qualifier(String code, String description) {
this.code = code;
this.description = description;
}
public String getCode() {
return this.code;
}
public String getDescription() {
return this.description;
}
private static class Adapter extends CamelCaseEnumAdapter<Qualifier> {
public Adapter() {
super(Qualifier.class, FOO);
}
}
}
Адаптер
public abstract class CamelCaseEnumAdapter<E extends Enum> extends XmlAdapter<String, E>{
private Class<E> clazz;
private E defaultValue;
public CamelCaseEnumAdapter(Class<E> clazz) {
this(clazz, null);
}
public CamelCaseEnumAdapter(Class<E> clazz, E defaultValue) {
this.clazz = clazz;
this.defaultValue = defaultValue;
}
@Override
@SuppressWarnings("unchecked")
public E unmarshal(String v) throws Exception {
if(v == null || v.isEmpty())
return defaultValue;
return (E) Enum.valueOf(clazz, v.replaceAll("([a-z])([A-Z])", "$1_$2").toUpperCase());
}
@Override
public String marshal(E v) throws Exception {
if(v == defaultValue)
return null;
return toCamelCase(v.name());
}
private String toCamelCase(String s){
String[] parts = s.split("_");
String camelCaseString = "";
for (String part : parts){
if(camelCaseString.isEmpty())
camelCaseString = camelCaseString + part.toLowerCase();
else
camelCaseString = camelCaseString + toProperCase(part);
}
return camelCaseString;
}
private String toProperCase(String s) {
return s.substring(0, 1).toUpperCase() +
s.substring(1).toLowerCase();
}
}