Подтвердить что ты не робот

Предоставление сериализации пользовательских значений для перечислений через 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 перечислимых значений, а редактирование вручную это не очень хорошо. Мы также думаем использовать пользовательский сериализатор вместо этого, но я бы хотел избежать этого маршрута на данный момент (но если это так, то у меня нет проблем с ним).

Любые идеи как?

4b9b3361

Ответ 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();
    }
}