Предоставление пользовательской сериализации значений для перечислений через JAXB - PullRequest
22 голосов
/ 11 января 2011

Для проекта, над которым я работаю, мы используем много перечислений.Сам объект модели состоит из множества крошечных классов;эту модель мы затем сериализуем в нашу БД в виде XML через JAXB.Теперь мы хотим иметь возможность сериализовать наши значения перечисления, используя возвращение определенного метода в перечислении;что дано:

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

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

Ответы [ 2 ]

24 голосов
/ 11 января 2011

Попробуйте использовать для этого механизм XmlAdapter. Вы создаете подкласс XmlAdapter для каждого типа перечисления, который знает, как упорядочить / разархивировать перечисление в и из 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;
4 голосов
/ 17 июня 2014

Нашел этот вопрос, когда искал что-то еще, но я прочитал ваш комментарий о чем-то более общем.Вот то, что я использовал для преобразования типов enum в верхнем регистре в регистр верблюдов.Я собираюсь использовать ваш тип 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();
    }
}
...