Jackson renomeia o campo booleano primitivo removendo 'é'

97

Pode ser uma duplicata. Mas não consigo encontrar uma solução para o meu problema.

Eu tenho uma aula

public class MyResponse implements Serializable {

    private boolean isSuccess;

    public boolean isSuccess() {
        return isSuccess;
    }

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

Getters e setters são gerados pelo Eclipse.

Em outra classe, defino o valor como true e o escrevo como uma string JSON.

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

Em JSON, a chave está chegando {"success": true}.

Eu quero a chave como isSuccessela mesma. Jackson está usando o método setter durante a serialização? Como transformar a chave no próprio nome do campo?

iCode
fonte
1
se o nome da sua propriedade for como isSuccesso nome do seu método deve ser isIsSuccesseu acho
Jens
Compreendo. Achei melhor SetSuccess já que foi gerado pelo Eclipse. (Seguindo um padrão)
iCode

Respostas:

121

Esta é uma resposta um pouco tardia, mas pode ser útil para qualquer pessoa que visite esta página.

Uma solução simples para alterar o nome que Jackson usará ao serializar para JSON é usar a anotação @JsonProperty , portanto, seu exemplo seria:

public class MyResponse implements Serializable {

    private boolean isSuccess;

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

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

Isso seria então serializado para JSON como {"isSuccess":true}, mas tem a vantagem de não precisar modificar o nome do método getter.

Observe que, neste caso, você também pode escrever a anotação, @JsonProperty("isSuccess")pois ela só tem um único valueelemento

Scott
fonte
Este método não funcionará no meu caso, uma vez que a classe não é minha, visto que vem de dependências de terceiros. Para esse caso, veja minha resposta abaixo.
edmundpie
5
Estou usando spring boot com jackson, mas obtendo dois campos, um é "sucesso" e o outro é "isSuccess" e quando eu uso um booleano não primitivo, apenas um campo "isSuccess"
Vishal Singla
@VishalSingla Tenho exatamente o mesmo problema, esta solução produz dois campos no Spring Boot
Aron Fiechter
serialização com GSON funciona bem.
Nayeem
23

Recentemente, encontrei esse problema e foi isso que descobri. Jackson inspecionará qualquer classe que você passar para ele em busca de getters e setters e usará esses métodos para serialização e desserialização. O que segue "get", "is" e "set" nesses métodos será usado como a chave para o campo JSON ("isValid" para getIsValid e setIsValid).

public class JacksonExample {   

    private boolean isValid = false;

    public boolean getIsValid() {
        return isValid;
    }

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

Da mesma forma, "isSuccess" se tornará "sucesso", a menos que seja renomeado para "isIsSuccess" ou "getIsSuccess"

Leia mais aqui: http://www.citrine.io/blog/2015/5/20/jackson-json-processor

Utkarsha Padhye
fonte
6
isValid não é a convenção de nomenclatura correta para o tipo de dados booleano em java. deve ser válido e isValid (), setValid ()
vels4j 01 de
2
mas não deveria ser exatamente isso? Uma convenção? Se ele existir, você poderia vincular a referência de Jackson que diz que ele usa nomes getter como os campos JSON? Ou você acha que é uma má escolha de design?
Abhinav Vishak
2
Gostaria que houvesse um aviso para isso
RyPope
@ vels4j As convenções de nomenclatura saem pela janela quando você está lidando com implementações altamente específicas.
Dragas,
13

Usando as duas anotações abaixo, força o JSON de saída a incluir is_xxx:

@get:JsonProperty("is_something")
@param:JsonProperty("is_something")
Fabio
fonte
Esta é a melhor resposta para esta pergunta.
dustinevan de
2
Isso é Java? Talvez seja Kotlin?
spottedmahn
7

Você pode configurar o seu da ObjectMapperseguinte maneira:

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);
            }
        });
burak emre
fonte
1
Gosto que você esteja tentando resolver isso via configuração. No entanto, isso só funcionará se você sempre prefixar seus campos booleanos e propriedades JSON com "is". Digamos que você tenha outro campo booleano simplesmente denominado "ativado" que deseja serializar como tal. Como o método gerado é "isEnabled ()", o código acima o serializará para "isEnabled" em vez de apenas "enabled". Em última análise, o problema é que para ambos os campos "x" e "isX", o Eclipse gera o método "isX ()"; portanto, você não pode inferir um nome de propriedade que corresponda ao campo.
David Siegal
@DavidSiegal base on burak answer Estendi a resposta abaixo para apoiar esse caso.
edmundpie
5

Quando você estiver usando Kotlin e classes de dados:

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

Talvez seja necessário adicionar @param:JsonProperty("isSuccess")se você for desserializar JSON também.

Eirik Fosse
fonte
2

Com base na resposta de Utkarsh ..

Nomes de getter menos get / is é usado como o nome JSON.

public class Example{
    private String radcliffe; 

    public getHarryPotter(){
        return radcliffe; 
    }
}

é armazenado como {"harryPotter": "qualquer que seja YouGaveHere"}


Para desserialização, Jackson verifica o setter e o nome do campo. Para a String Json {"palavra1": "exemplo"} , ambas as opções abaixo são válidas.

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 ; 
    }
}

Uma questão mais interessante é qual ordem Jackson considera para desserialização. Se eu tentar desserializar {"word1": "myName"} com

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

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

Não testei o caso acima, mas seria interessante ver os valores de palavra1 e palavra2 ...

Observação: usei nomes drasticamente diferentes para enfatizar quais campos devem ser iguais.

Abhinav Vishak
fonte
2

Eu não queria mexer com algumas estratégias de nomenclatura personalizadas, nem recriar alguns acessadores.
Quanto menos código, mais feliz eu fico.

Isso funcionou para nós:

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;

}
Adrien
fonte
1

existe outro método para este problema.

basta definir uma nova subclasse que estende PropertyNamingStrategy e passá-la para a instância ObjectMapper.

aqui está um snippet de código que pode ser mais útil:

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;
        }
    });
Eric
fonte
1

A resposta aceita não funcionará para o meu caso.

No meu caso, a classe não é minha. A classe problemática vem de dependências de terceiros, então não posso simplesmente adicionar @JsonPropertyanotações a ela.

Para resolvê-lo, inspirado na resposta de @burak acima, criei um custom PropertyNamingStrategycomo segue:

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);
  }
});

Basicamente, o que isso faz é, antes de serializar e desserializar, verificar na classe de destino / origem qual nome de propriedade está presente na classe, se é isEnabledou enabledpropriedade.

Com base nisso, o mapeador será serializado e desserializado para o nome da propriedade existente.

Edmundpie
fonte
0

Você pode alterar o booleano primitivo para java.lang.Boolean (+ usar @JsonPropery)

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

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

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

Funcionou excelente para mim.

Reynard
fonte