Джексон переименовывает примитивное логическое поле, удаляя 'is'

93

Это может быть дубликат. Но я не могу найти решение своей проблемы.

У меня есть класс

public class MyResponse implements Serializable {

    private boolean isSuccess;

    public boolean isSuccess() {
        return isSuccess;
    }

    public void setSuccess(boolean isSuccess) {
        this.isSuccess = isSuccess;
    }
}

Геттеры и сеттеры генерируются Eclipse.

В другом классе я устанавливаю значение true и записываю его как строку JSON.

System.out.println(new ObjectMapper().writeValueAsString(myResponse));

В JSON ключ выглядит как {"success": true}.

Я хочу, чтобы ключ был isSuccessсам по себе. Использует ли Джексон метод установки при сериализации? Как сделать ключ именем самого поля?

iCode
источник
1
Если ваше имя свойства - isSuccessэто, isIsSuccessя думаю, имя вашего метода должно быть таким,
Йенс,
Я понимаю. Я подумал, что лучше, так SetSuccess как он генерируется Eclipse. (По стандарту)
iCode

Ответы:

119

Это немного запоздалый ответ, но он может быть полезен всем, кто заходит на эту страницу.

Простым решением для изменения имени, которое Джексон будет использовать при сериализации в JSON, является использование аннотации @JsonProperty , поэтому ваш пример будет выглядеть следующим образом:

public class MyResponse implements Serializable {

    private boolean isSuccess;

    @JsonProperty(value="isSuccess")        
    public boolean isSuccess() {
        return isSuccess;
    }

    public void setSuccess(boolean isSuccess) {
        this.isSuccess = isSuccess;
    }
}

Затем он будет сериализован в JSON as {"isSuccess":true}, но имеет то преимущество, что вам не нужно изменять имя вашего метода получения.

Обратите внимание, что в этом случае вы также можете написать аннотацию, @JsonProperty("isSuccess")поскольку она имеет только один valueэлемент

Скотт
источник
Этот метод не будет работать в моем случае, так как класс не принадлежит мне, поскольку он исходит из сторонних зависимостей. В таком случае см. Мой ответ ниже.
edmundpie
4
Я использую весеннюю загрузку с Джексоном, но получаю два поля: одно - «успех», а другое - «isSuccess», и когда я использую не примитивное логическое значение, то только одно поле «isSuccess»
Вишал Сингла
@VishalSingla У меня такая же проблема, это решение создает два поля в Spring Boot
Арон Фихтер
22

Недавно я столкнулся с этой проблемой, и вот что я нашел. Джексон проверит любой передаваемый ему класс на предмет геттеров и сеттеров и будет использовать эти методы для сериализации и десериализации. То, что следует за «get», «is» и «set» в этих методах, будет использоваться в качестве ключа для поля JSON («isValid» для getIsValid и setIsValid).

public class JacksonExample {   

    private boolean isValid = false;

    public boolean getIsValid() {
        return isValid;
    }

    public void setIsValid(boolean isValid) {
        this.isValid = isValid;
    }
} 

Точно так же isSuccess станет «success», если не будет переименовано в isIsSuccess или getIsSuccess.

Подробнее здесь: http://www.citrine.io/blog/2015/5/20/jackson-json-processor

Уткарша Падхье
источник
6
isValid - неправильное соглашение об именах для логического типа данных в java. должен быть действительным и isValid (), setValid ()
vels4j 01
2
но разве это не должно быть именно так? Соглашение? Если он существует, не могли бы вы сослаться на ссылку Джексона, в которой говорится, что в качестве полей JSON используются имена получателей? Или вы думаете, что это плохой выбор дизайна?
Абхинав Вишак
2
Я бы хотел, чтобы за это было предупреждение
RyPope
@ vels4j Соглашения об именах теряют смысл, когда вы имеете дело с очень специфическими реализациями.
Драгас
13

Используя обе аннотации ниже, вынуждает включать в выходной JSON is_xxx:

@get:JsonProperty("is_something")
@param:JsonProperty("is_something")
Фабио
источник
Это лучший ответ на этот вопрос.
dustinevan
1
Это Java? Может это Котлин?
spottedmahn
5

Вы можете настроить ObjectMapperследующее:

mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
            @Override
            public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
            {
                if(method.hasReturnType() && (method.getRawReturnType() == Boolean.class || method.getRawReturnType() == boolean.class)
                        && method.getName().startsWith("is")) {
                    return method.getName();
                }
                return super.nameForGetterMethod(config, method, defaultName);
            }
        });
Бурак Эмре
источник
1
Мне нравится, что вы пытаетесь решить эту проблему с помощью конфигурации. Однако это будет работать только в том случае, если вы всегда добавляете к логическим полям и свойствам JSON префикс «is». Допустим, у вас есть еще одно логическое поле с именем «enabled», которое вы хотите сериализовать как таковое. Поскольку сгенерированным методом является isEnabled (), приведенный выше код затем сериализует его в isEnabled, а не просто «включен». В конечном итоге проблема в том, что для обоих полей «x» и «isX» Eclipse генерирует метод «isX ()»; поэтому вы не можете вывести имя свойства, соответствующее полю.
Дэвид Сигал
База @DavidSiegal на ответе burak Я расширил ответ ниже, чтобы поддержать такой случай.
edmundpie
4

Когда вы используете Kotlin и классы данных:

data class Dto(
    @get:JsonProperty("isSuccess") val isSuccess: Boolean
)

Возможно, вам придется добавить, @param:JsonProperty("isSuccess")если вы также собираетесь десериализовать JSON.

Эйрик Фоссе
источник
2

Основываясь на ответе Уткарша ..

Имена получателей минус get / используются в качестве имени JSON.

public class Example{
    private String radcliffe; 

    public getHarryPotter(){
        return radcliffe; 
    }
}

хранится как {"harryPotter": "somethingYouGaveHere"}


Для десериализации Джексон проверяет как установщик, так и имя поля. Для строки Json {"word1": "example"} допустимы оба приведенных ниже значения.

public class Example{
    private String word1; 

    public setword2( String pqr){
        this.word1 = pqr; 
    }
}

public class Example2{
    private String word2; 

    public setWord1(String pqr){
        this.word2 = pqr ; 
    }
}

Более интересный вопрос - какой порядок Джексон рассматривает для десериализации. Если я попытаюсь десериализовать {"word1": "myName"} с помощью

public class Example3{
    private String word1;
    private String word2; 

    public setWord1( String parameter){
        this.word2 = parameter ; 
    }
}

Я не проверял приведенный выше случай, но было бы интересно увидеть значения word1 и word2 ...

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

Абхинав Вишак
источник
1

есть другой способ решения этой проблемы.

просто определите новый подкласс extends PropertyNamingStrategy и передайте его экземпляру ObjectMapper.

вот фрагмент кода может помочь больше:

mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
        @Override
        public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
            String input = defaultName;
            if(method.getName().startsWith("is")){
                input = method.getName();
            }

            //copy from LowerCaseWithUnderscoresStrategy
            if (input == null) return input; // garbage in, garbage out
            int length = input.length();
            StringBuilder result = new StringBuilder(length * 2);
            int resultLength = 0;
            boolean wasPrevTranslated = false;
            for (int i = 0; i < length; i++)
            {
                char c = input.charAt(i);
                if (i > 0 || c != '_') // skip first starting underscore
                {
                    if (Character.isUpperCase(c))
                    {
                        if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_')
                        {
                            result.append('_');
                            resultLength++;
                        }
                        c = Character.toLowerCase(c);
                        wasPrevTranslated = true;
                    }
                    else
                    {
                        wasPrevTranslated = false;
                    }
                    result.append(c);
                    resultLength++;
                }
            }
            return resultLength > 0 ? result.toString() : input;
        }
    });
Эрик
источник
1

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

Это помогло нам:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonIgnoreProperties({"success", "deleted"}) // <- Prevents serialization duplicates 
public class MyResponse {

    private String id;
    private @JsonProperty("isSuccess") boolean isSuccess; // <- Forces field name
    private @JsonProperty("isDeleted") boolean isDeleted;

}
Адриан
источник
1

Принятый ответ не сработает в моем случае.

В моем случае класс не принадлежит мне. Проблемный класс исходит из сторонних зависимостей, поэтому я не могу просто добавить @JsonPropertyв него аннотацию.

Чтобы решить эту проблему, вдохновившись ответом @burak выше, я создал следующий обычай PropertyNamingStrategy:

mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
  @Override
  public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
  {
    if (method.getParameterCount() == 1 &&
            (method.getRawParameterType(0) == Boolean.class || method.getRawParameterType(0) == boolean.class) &&
            method.getName().startsWith("set")) {

      Class<?> containingClass = method.getDeclaringClass();
      String potentialFieldName = "is" + method.getName().substring(3);

      try {
        containingClass.getDeclaredField(potentialFieldName);
        return potentialFieldName;
      } catch (NoSuchFieldException e) {
        // do nothing and fall through
      }
    }

    return super.nameForSetterMethod(config, method, defaultName);
  }

  @Override
  public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
  {
    if(method.hasReturnType() && (method.getRawReturnType() == Boolean.class || method.getRawReturnType() == boolean.class)
        && method.getName().startsWith("is")) {

      Class<?> containingClass = method.getDeclaringClass();
      String potentialFieldName = method.getName();

      try {
        containingClass.getDeclaredField(potentialFieldName);
        return potentialFieldName;
      } catch (NoSuchFieldException e) {
        // do nothing and fall through
      }
    }
    return super.nameForGetterMethod(config, method, defaultName);
  }
});

В основном, это то, что перед сериализацией и десериализацией он проверяет в целевом / исходном классе, какое имя свойства присутствует в классе, будь то свойство isEnabledили enabledсвойство.

Исходя из этого, преобразователь будет сериализовать и десериализовать существующее имя свойства.

Эдмундпи
источник
0

Вы можете изменить примитивное логическое значение на java.lang.Boolean (+ использовать @JsonPropery)

@JsonProperty("isA")
private Boolean isA = false;

public Boolean getA() {
    return this.isA;
}

public void setA(Boolean a) {
    this.isA = a;
}

У меня отлично сработало.

Рейнард
источник