Пост-конструкция Джексона Маппера

Я использую Jackson ObjectMapper для десериализации некоторого JSON в класс Java, который мы назовем PlayerData. Я хотел бы добавить немного логики в класс PlayerData, чтобы исправить некоторые данные после того, как поля были загружены. Например, в некоторых ранних файлах JSON использовался флаг «пол» вместо «пол» falg, поэтому если флаг пола установлен, но флаг пола не установлен, я хотел бы установить значение поля пола как значение поля пола.

Есть ли какая-то аннотация @PostConstruct или @AfterLoad, которую я мог бы прикрепить к методу? Или, возможно, интерфейс, который я мог бы реализовать? Я не заметил его в документации, но это казалось очевидной функцией.


person Brandon Yarbrough    schedule 26.07.2011    source источник
comment
Это предложение прекрасно сработало для меня как замена @PostConstruct   -  person fedor.belov    schedule 11.06.2015


Ответы (4)


Нашел это по ссылке в комментариях (кредит: fedor.belov). Похоже, это позволяет вам запускать конструкцию кода post.

Добавление комментария для людей, которые попадают сюда через http://jira.codehaus.org/browse/JACKSON-645 или http://jira.codehaus.org/browse/JACKSON-538 и ищем метод, который вызывается после завершения десериализатора. Я смог добиться желаемого эффекта, включив аннотацию и написав конвертер, который использует тот же класс для ввода и вывода.

@JsonDeserialize(converter=MyClassSanitizer.class)  // invoked after class is fully deserialized
public class MyClass {
    public String field1;
}

import com.fasterxml.jackson.databind.util.StdConverter;
public class MyClassSanitizer extends StdConverter<MyClass,MyClass> {
  @Override
  public MyClass convert(MyClass var1) {
    var1.field1 = munge(var1.field1);
    return var1;
  }
}
person Mike Rylander    schedule 28.11.2016
comment
Стоит отметить, что вместо этого может потребоваться класс static. См. stackoverflow.com/a/54605694/885922. - person xlm; 30.11.2020

Если вы не используете @JsonCreator, тогда Джексон будет использовать методы установки и получения для установки полей.

Итак, если вы определяете следующие методы, предполагая, что у вас есть перечисления Sex и Gender:

@JsonProperty("sex")
public void setSex(final Sex sex) {
  this.sex = sex;
  if (gender == null) {
    gender = (sex == Sex.WOMAN) ? Gender.WOMAN : Gender.MAN;
  }
}

@JsonProperty("gender")
public void setGender(final Gender gender) {
  this.gender = gender;
  if (sex == null) {
    sex = (gender == Gender.WOMAN) ? Sex.WOMAN : Sex.MAN;
  }
}

это сработает.

Обновление: вы можете найти все аннотации библиотеки Джексона здесь.

Update2. Другое решение:

class Example {
  private final Sex sex;
  private final Gender gender;

  @JsonCreator
  public Example(@JsonProperty("sex") final Sex sex) {
    super();
    this.sex = sex;
    this.gender = getGenderBySex(sex)
  }

  @JsonFactory
  public static Example createExample(@JsonProperty("gender") final Gender gender) {
    return new Example(getSexByGender(gender));
  }

  private static Sex getSexByGender(final Gender) {
    return (gender == Gender.WOMAN) ? Sex.WOMAN : Sex.MAN;
  }

  private static Gender getGenderBySex(final Sex) {
    return (sex == Sex.WOMAN) ? Gender.WOMAN : Gender.MAN;
  }

}
person KARASZI István    schedule 26.07.2011
comment
Вам даже не нужен JsonProperty, поскольку он по умолчанию обнаруживает все геттеры (включая isSomething()) и сеттеры, которые можно использовать для сериализации/десериализации. - person pingw33n; 27.07.2011
comment
@JsonConstructor не указан в этом списке всех предоставленных вами аннотаций :( - person Brandon Yarbrough; 27.07.2011
comment
Возможно, он имел в виду @JsonCreator..., который можно использовать для указания альтернативного конструктора (или фабричного метода внутри класса) для использования, а также для согласования значений нескольких полей. - person StaxMan; 27.07.2011
comment
AFAIK разрешен только один аннотированный конструктор @JsonCreator для класса. Таким образом, один @JsonFactory фабричный метод и один @JsonCreator конструктор могут решить проблему. - person KARASZI István; 27.07.2011

Это не поддерживается из коробки, но вы можете легко создать свою аннотацию @JsonPostDeserialize для методов, которые будут вызываться после десериализации.

Сначала определите аннотацию:

/**
 * Annotation for methods to be called directly after deserialization of the object.
 */
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonPostDeserialize {
}

Затем добавьте в проект следующий код регистрации и реализации:

public static void addPostDeserializeSupport(ObjectMapper objectMapper) {
    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new BeanDeserializerModifier() {
        @Override
        public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDescription,
                JsonDeserializer<?> originalDeserializer) {
            return new CustomAnnotationsDeserializer(originalDeserializer, beanDescription);
        }
    });
    objectMapper.registerModule(module);
}

/**
 * Class implementing the functionality of the {@link JsonPostDeserialize} annotation.
 */
public class CustomAnnotationsDeserializer extends DelegatingDeserializer {
    private final BeanDescription beanDescription;

    public CustomAnnotationsDeserializer(JsonDeserializer<?> delegate, BeanDescription beanDescription) {
        super(delegate);
        this.beanDescription = beanDescription;
    }

    @Override
    protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
        return new CustomAnnotationsDeserializer(newDelegatee, beanDescription);
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        Object deserializedObject = super.deserialize(p, ctxt);

        callPostDeserializeMethods(deserializedObject);
        return deserializedObject;
    }

    private void callPostDeserializeMethods(Object deserializedObject) {
        for (AnnotatedMethod method : beanDescription.getClassInfo().memberMethods()) {
            if (method.hasAnnotation(JsonPostDeserialize.class)) {
                try {
                    method.callOn(deserializedObject);
                } catch (Exception e) {
                    throw new RuntimeException("Failed to call @JsonPostDeserialize annotated method in class "
                            + beanDescription.getClassInfo().getName(), e);
                }
            }
        }
    }
}

Наконец, измените свой экземпляр ObjectMapper на addPostDeserializeSupport, он вызовет все @JsonPostDeserialize аннотированные методы десериализованных объектов.

person oberlies    schedule 02.09.2019
comment
Лучший ответ. Также помните, что метод, к которому вы применяете аннотацию, должен быть public. - person end-user; 05.06.2021

Это то, что на самом деле было предложено пару раз ранее. Так что, возможно, подача RFE имеет смысл; есть несколько способов, которыми это могло бы работать: очевидными являются возможность аннотировать тип (@JsonPostProcess(Processor.class)) и возможность зарегистрировать постпроцессор через API модуля (так что в основном есть обратный вызов, когда Джексон создает десериализатор, чтобы позволить модуль указывает используемый постпроцессор, если таковой имеется). Но, возможно, есть еще лучшие способы сделать это.

person StaxMan    schedule 26.07.2011