Jackson cambia el nombre del campo booleano primitivo eliminando 'es'

93

Este podría ser un duplicado. Pero no puedo encontrar una solución a mi problema.

Tengo una clase

public class MyResponse implements Serializable {

    private boolean isSuccess;

    public boolean isSuccess() {
        return isSuccess;
    }

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

Eclipse genera los getters y setters.

En otra clase, establezco el valor en verdadero y lo escribo como una cadena JSON.

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

En JSON, la clave viene como {"success": true}.

Quiero la llave como isSuccessella misma. ¿Está Jackson usando el método setter durante la serialización? ¿Cómo hacer que la clave sea el nombre del campo?

iCode
fuente
1
si el nombre de su propiedad es liek isSuccess, isIsSuccesscreo que el nombre del método debe ser
Jens
Entiendo. Pensé que es mejor SetSuccess porque lo genera Eclipse. (Siguiendo un estándar)
iCode

Respuestas:

119

Esta es una respuesta un poco tardía, pero puede ser útil para cualquier otra persona que acceda a esta página.

Una solución simple para cambiar el nombre que Jackson usará al serializar a JSON es usar la anotación @JsonProperty , por lo que su ejemplo sería:

public class MyResponse implements Serializable {

    private boolean isSuccess;

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

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

Esto luego se serializaría en JSON como {"isSuccess":true}, pero tiene la ventaja de no tener que modificar el nombre del método getter.

Tenga en cuenta que en este caso también puede escribir la anotación, @JsonProperty("isSuccess")ya que solo tiene un valueelemento

Scott
fuente
Este método no funcionará para mi caso, ya que la clase no es de mi propiedad, ya que proviene de dependencias de terceros. Para tal caso, vea mi respuesta a continuación.
edmundpie
4
Estoy usando spring boot con jackson, pero obteniendo dos campos, uno es "éxito" y el otro es "isSuccess" y cuando uso un booleano no primitivo, solo un campo "isSuccess"
Vishal Singla
@VishalSingla Tengo exactamente el mismo problema, esta solución produce dos campos en Spring Boot
Aron Fiechter
22

Recientemente me encontré con este problema y esto es lo que encontré. Jackson inspeccionará cualquier clase que le pase en busca de getters y setters, y utilizará esos métodos para la serialización y deserialización. Lo que sigue a "get", "is" y "set" en esos métodos se utilizará como clave para el campo JSON ("isValid" para getIsValid y setIsValid).

public class JacksonExample {   

    private boolean isValid = false;

    public boolean getIsValid() {
        return isValid;
    }

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

De manera similar, "isSuccess" se convertirá en "success", a menos que se cambie el nombre a "isIsSuccess" o "getIsSuccess"

Lea más aquí: http://www.citrine.io/blog/2015/5/20/jackson-json-processor

Utkarsha Padhye
fuente
6
isValid no es una convención de nomenclatura correcta para el tipo de datos booleanos en java. debe ser válido y isValid (), setValid ()
vels4j
2
pero ¿no se supone que sea exactamente eso? Una convención ? Si existe, ¿podría vincular a la referencia de Jackson que dice que usa nombres de captador como campos JSON? ¿O crees que es una mala elección de diseño?
Abhinav Vishak
2
Ojalá hubiera una advertencia para esto
RyPope
@ vels4j Las convenciones de nomenclatura desaparecen cuando se trata de implementaciones muy específicas.
Dragas
13

El uso de ambas anotaciones a continuación obliga al JSON de salida a incluir is_xxx:

@get:JsonProperty("is_something")
@param:JsonProperty("is_something")
Fabio
fuente
Esta es la mejor respuesta a esta pregunta.
dustinevan
1
¿Eso es Java? ¿Quizás sea Kotlin?
spottedmahn
5

Puede configurar su de la ObjectMappersiguiente manera:

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
fuente
1
Me gusta que intentes resolver esto mediante la configuración. Sin embargo, esto solo funcionará si siempre antepone sus campos booleanos y propiedades JSON con "is". Supongamos que tiene otro campo booleano simplemente llamado "habilitado" que desea serializar como tal. Debido a que el método generado es "isEnabled ()", el código anterior lo serializará a "isEnabled" en lugar de simplemente "habilitado". En última instancia, el problema es que para ambos campos "x" e "isX", Eclipse genera el método "isX ()"; por lo que no puede inferir un nombre de propiedad que coincida con el campo.
David Siegal
@DavidSiegal basado en la respuesta de burak He extendido la respuesta a continuación para respaldar tal caso.
edmundpie
4

Cuando usa Kotlin y clases de datos:

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

Es posible que deba agregar @param:JsonProperty("isSuccess")si también va a deserializar JSON.

Eirik Fosse
fuente
2

Sobre la base de la respuesta de Utkarsh.

Los nombres de getter menos get / is se utilizan como nombre JSON.

public class Example{
    private String radcliffe; 

    public getHarryPotter(){
        return radcliffe; 
    }
}

se almacena como {"harryPotter": "lo que sea que tengas aquí"}


Para deserialización, Jackson verifica tanto el colocador como el nombre del campo. Para la cadena Json {"word1": "example"} , ambos siguientes son válidos.

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

Una pregunta más interesante es qué orden considera Jackson para la deserialización. Si intento deserializar {"word1": "myName"} con

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

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

No probé el caso anterior, pero sería interesante ver los valores de word1 y word2 ...

Nota: Utilicé nombres drásticamente diferentes para enfatizar qué campos deben ser iguales.

Abhinav Vishak
fuente
1

hay otro método para este problema.

simplemente defina una nueva subclase extiende PropertyNamingStrategy y pásala a la instancia de ObjectMapper.

aquí hay un fragmento de código que puede ser más ú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
fuente
1

No quería meterme con algunas estrategias de nomenclatura personalizadas, ni volver a crear algunos accesos.
Cuanto menos código, más feliz soy.

Esto nos sirvió:

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
fuente
1

La respuesta aceptada no funcionará para mi caso.

En mi caso, la clase no me pertenece. La clase problemática proviene de dependencias de terceros, por lo que no puedo simplemente agregar una @JsonPropertyanotación en ella.

Para resolverlo, inspirado en la respuesta de @burak anterior, creé una personalizada de la PropertyNamingStrategysiguiente manera:

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

Básicamente, lo que hace es, antes de serializar y deserializar, verifica en la clase de destino / fuente qué nombre de propiedad está presente en la clase, si es isEnabledo enabledpropiedad.

En base a eso, el asignador serializará y deserializará el nombre de propiedad que existe.

edmundpie
fuente
0

Puede cambiar el booleano primitivo a 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;
}

Funcionó excelente para mí.

Reynard
fuente