Десериализация строки json в изменчивые объекты с помощью genson

Я пытаюсь преобразовать простой массив строковых элементов в документе json в список java, который находится внутри компонента с использованием genson. Я ожидал, что это произойдет автоматически, но это не работает. Я что-то упускаю?

Вот моя Json-строка:

{"id":12345,"permissions":["READ_XX","WRITE_XX"]}

И вот вывод моего объекта, показывающий, что мой список разрешений не заполнен:

DefaultRole [id=12345, name=null, permissions=[]]

Мой класс DefaultRole выглядит так:

public class DefaultRole implements Role
{
  public Id id = null;

  @XmlElementWrapper(name = "permissions")
  @XmlElement(name = "permission")
  private List<String> permissions = Lists.newArrayList();

  @Inject
  public DefaultRole()
  {

  }
  [...]

Аннотации JAXB предназначены только для преобразования XML. Здесь они не должны играть роли.

Я создаю объект Genson следующим образом:

Genson genson = new Genson.Builder()
  .setWithClassMetadata(false)
  .setWithBeanViewConverter(true)
  .setWithDebugInfoPropertyNameResolver(true)
  .setSkipNull(true)
  .create();

Мой вызов десериализации:

genson().deserialize(jsonString, DefaultRole.class)

Есть ли у кого-нибудь совет о том, как я могу десериализовать массив строк json в объект списка java, находящийся внутри компонента?

РЕДАКТИРОВАТЬ [РЕШЕНО ЧАСТИЧНО - ЕЩЕ ОДИН ВОПРОС ОТКРЫТ]

Теперь у меня есть шаг вперед. Я вернулся к истокам и начал с простого bean-компонента, который просто содержит список строк. И, как и ожидалось в первый раз, это работает. Разница между предыдущим объектом в основном в том, что этот следует спецификации bean-компонента. Другой объект (DefaultRole) следует шаблону текучего объекта (не уверен, что это правильное определение). По сути, вместо того, чтобы метод setter был пустым, я возвращаю объект, так что следующее значение можно легко установить плавным образом.

Итак, это работает:

public void setPermissions(List<String> permissions)
{
  this.permissions = permissions;
}

Это не работает:

public Role setPermissions(List<String> permissions)
{
  this.permissions = permissions;
  return this;
}

Кто-нибудь еще сталкивался с этим и каковы альтернативы переключению всех моих bean-компонентов на соответствие спецификации bean-компонентов? Является ли единственным вариантом использовать чистое заполнение на уровне поля вместо метода установки?

РЕДАКТИРОВАТЬ [ПОЧТИ РЕШЕНО]

Привет, я не уверен, как лучше всего отвечать на вопросы, где код необходим, чтобы помочь понять, что я сделал.

В любом случае, большое спасибо за вашу помощь. Мне очень нравится третий вариант, именно его я и искал. К сожалению, теперь я получаю сообщение об ошибке «Не найден конструктор для класса типов xxx.DefaultRole». Согласно тому, что вы сказали в своем ответе, этого не должно происходить при возврате Trilean.UNKNOWN, поскольку затем поиск продолжается.

Я добавляю следующий код в свой genson-builder:

        .set(new BeanMutatorAccessorResolver.BaseResolver()
        {
            @Override
            public Trilean isMutator(Method method, Class<?> fromClass)
            {
                if (Reflect.isSetter(method))
                    return Trilean.TRUE;

                else
                    return Trilean.UNKNOWN;
            }
        })

Мой Reflect.isSetter(метод) выглядит так (код адаптирован отсюда: http://www.asgteach.com/blog/?p=559):

public static boolean isSetter(Method method)
{
    return Modifier.isPublic(method.getModifiers()) &&
        (method.getReturnType().equals(void.class) || method.getReturnType().equals(method.getDeclaringClass())) &&
        method.getParameterTypes().length == 1 &&
        method.getName().matches("^set[A-Z].*");
}

BaseResolver возвращает Trilean.UNKNOWN для всего, что не было реализовано. Следовательно, он должен найти конструктор, используя стандартную логику, не так ли?

РЕДАКТИРОВАТЬ [РЕШЕНО]

Просто для полноты я опубликую код, который действительно работает:

public static final Genson genson()
{
    Genson genson = new Genson.Builder()
        .setSkipNull(true)
        .setWithClassMetadata(false)
        .setWithDebugInfoPropertyNameResolver(true)
        .setWithBeanViewConverter(true)
        .with(new BeanMutatorAccessorResolver.BaseResolver()
        {
            @Override
            public Trilean isMutator(Method method, Class<?> fromClass)
            {
                if (Reflect.isSetter(method))
                    return Trilean.TRUE;

                else
                    return Trilean.UNKNOWN;
            }
        })
        .create();

    return genson;
}

Здесь важно отметить, что «.set(new BeanMutatorAccessorResolver.BaseResolver()» необходимо заменить на «.with(new BeanMutatorAccessorResolver.BaseResolver()» (обратите внимание на «with» вместо «set»). Это важно поскольку стандартные Resolvers больше не используются в противном случае, и вы получите ошибку, которая была у меня, когда конструктор больше не может быть найден.

Метод isSetter выглядит так:

public static boolean isSetter(Method method)
{
    return Modifier.isPublic(method.getModifiers())
        && (method.getReturnType().equals(void.class) || method.getReturnType().isAssignableFrom(method.getDeclaringClass()))
        && method.getParameterTypes().length == 1
        && method.getName().matches("^set[A-Z].*");
}

Здесь важно отметить, что я изначально использовал «равно» вместо «isAssignableFrom» при сравнении возвращаемого типа с объявляющим классом. Это работает только в том случае, если возвращаемый тип точно такой же, как тот, в котором он объявлен. Однако при использовании интерфейса в качестве возвращаемого значения он больше не работает. Вместо этого с помощью «method.getReturnType().isAssignableFrom(method.getDeclaringClass())» это также работает для интерфейсов (включая суперинтерфейсы).

Спасибо, Майкл


person michaeldd    schedule 22.02.2014    source источник
comment
действительно должен, какую версию вы используете? (Кстати, вы должны добавить комментарий или что-то еще, чтобы люди получали уведомления о том, что у вас есть еще один вопрос и т. д.)   -  person eugen    schedule 23.02.2014
comment
вы также можете опубликовать сообщение в списке рассылки gensons с минимальным примером, воспроизводящим вашу проблему.   -  person eugen    schedule 23.02.2014
comment
Спасибо, я использую 0,98.   -  person michaeldd    schedule 23.02.2014
comment
Хорошо, в следующий раз сделаю, спасибо.   -  person michaeldd    schedule 23.02.2014
comment
Хм, не могу воспроизвести вашу проблему. Это должно работать. Не могли бы вы опубликовать вопрос на ML с полным примером, чтобы воспроизвести это поведение (основной с настройкой genson + определение DefaultRole, поэтому мне нужно только запустить его, чтобы посмотреть, что произойдет)?   -  person eugen    schedule 23.02.2014
comment
Хорошо, сделаю, спасибо. На самом деле я еще ничего не менял - только добавил новый BeanMutatorAccessorResolver. Как только я удаляю это, кажется, что он снова работает (с методами void). Я отладил, чтобы увидеть, входит ли он в правильный блок if, и эта часть кажется в порядке.   -  person michaeldd    schedule 23.02.2014


Ответы (1)


Действительно, Genson (как и большинство других библиотек) использует соглашения Java bean для обнаружения установщика/получателя, поэтому он не обнаружит ваши setPermissions в качестве установщика.

Два быстрых решения:

1) Вы можете аннотировать его с помощью @JsonProperty (не нужно определять имя). Это скажет genson использовать его в качестве установщика (однако genson не будет повторно использовать возвращенный объект, что означает, что в вашем случае это работает, но билдер, как и гайд, поддерживается таким образом).

2) Вы можете заставить genson использовать только поля, а не методы (это напрямую устанавливает значение поля без вызова get/set).

Genson.Builder()
        .setUseFields(true)
        .setFieldFilter(VisibilityFilter.DEFAULT)
        .setUseGettersAndSetters(false)
      .create();

Более общий:

3) добавьте в цепочку пользовательский BeanMutatorAccessorResolver, который будет обрабатывать ваш конкретный случай

Genson genson = Genson.Builder().with(new BeanMutatorAccessorResolver.BaseResolver() {
        @Override
        public Trilean isMutator(Method method, Class<?> fromClass) {
            // if method starts with setXXX and return type same as method.getDeclaringClass
            return Trilean.TRUE;
            // else return Tilean.UNKNOWN genson will use its built-in resolvers
        }
    }).create();

ИЗМЕНИТЬ

Надо было заметить, что вы использовали метод set вместо метода with в конструкторе. Когда вы видите setXXX в genson API, это обычно означает, что вы переопределяете/принуждаете какой-то механизм (вы устанавливаете значение), но with используется для добавления поведения.

person eugen    schedule 23.02.2014
comment
Кстати, начиная с версии 0.95 genson имеет частичную поддержку аннотаций JAXB, так что вы могли бы даже используйте, чтобы иметь похожее поведение между json и xml ser/deser, просто сделайте несколько тестов, прежде чем убедиться, что он симметричен - person eugen; 23.02.2014
comment
Привет, я ответил на ваш вопрос в моем первоначальном вопросе. Я видел, что в genson есть поддержка JAXB, но я стараюсь как можно дальше идти по пути genson (и использовать как можно меньше аннотаций JAXB). Я использую это в сочетании с джерси, и мой предпочтительный способ - заставить джерси хорошо работать с genson, а не genson с JAXB. Я очень доволен Genson — мне нужно понять одну или две вещи, чтобы сделать его идеальным. - person michaeldd; 23.02.2014
comment
ДА, вы абсолютно правы, я увидел метод with и задумался... иногда лучше просто попробовать. setXX звучало так, как будто я устанавливаю (и переопределяю) исходное значение своим. Во всяком случае, теперь это работает, большое спасибо за ваше время! - person michaeldd; 23.02.2014