No puedo hacer que Jackson y Lombok trabajen juntos

91

Estoy experimentando combinando Jackson y Lombok. Esas son mis clases:

package testelombok;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.experimental.Wither;

@Value
@Wither
@AllArgsConstructor(onConstructor=@__(@JsonCreator))
public class TestFoo {
    @JsonProperty("xoom")
    private String x;
    private int z;
}
package testelombok;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xebia.jacksonlombok.JacksonLombokAnnotationIntrospector;
import java.io.IOException;

public class TestLombok {

    public static void main(String[] args) throws IOException {
        TestFoo tf = new TestFoo("a", 5);
        System.out.println(tf.withX("b"));
        ObjectMapper om = new ObjectMapper().setAnnotationIntrospector(new JacksonLombokAnnotationIntrospector());
        System.out.println(om.writeValueAsString(tf));
        TestFoo tf2 = om.readValue(om.writeValueAsString(tf), TestFoo.class);
        System.out.println(tf2);
    }

}

Esos son los archivos JAR que estoy agregando a la clase:

Lo estoy compilando con Netbeans (no creo que esto sea realmente relevante, pero lo estoy informando de todos modos para que sea perfecta y fielmente reproducible). Los cinco JAR anteriores se guardan en una carpeta llamada " lib" dentro de la carpeta del proyecto (junto con " src", " nbproject", " test" y " build"). Los agregué a Netbeans a través del botón " Agregar JAR / Carpeta " en las propiedades del proyecto y se enumeran en el orden exacto en la lista anterior. El proyecto es un proyecto de tipo "aplicación Java" estándar.

Además, el proyecto Netbeans está configurado para " NO compilar al guardar ", " generar información de depuración ", " informar API obsoletas ", " rastrear dependencias java ", " procesar anotaciones activacte " y " procesar anotaciones activacte en el editor ". No se configura explícitamente ningún procesador de anotaciones ni opción de procesamiento de anotaciones en Netbeans. Además, la -Xlint:allopción de línea de " " comandos se pasa en la línea de comandos del compilador y el compilador se ejecuta en una máquina virtual externa.

La versión de mi javac es 1.8.0_72 y la versión de mi java es 1.8.0_72-b15. Mi Netbeans es 8.1.

Mi proyecto se compila bien. Sin embargo, arroja una excepción en su ejecución. La excepción no parece ser nada que parezca fácil o obviamente reparable. Aquí está la salida, incluido el stacktrace:

TestFoo(x=b, z=5)
{"z":5,"xoom":"a"}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Argument #0 of constructor [constructor for testelombok.TestFoo, annotations: {interface java.beans.ConstructorProperties=@java.beans.ConstructorProperties(value=[x, z]), interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}] has no property name annotation; must have name when multiple-parameter constructor annotated as Creator
 at [Source: {"z":5,"xoom":"a"}; line: 1, column: 1]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:296)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:269)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
    at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
    at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:475)
    at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:3890)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3785)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2833)
    at testelombok.TestLombok.main(TestLombok.java:14)
Caused by: java.lang.IllegalArgumentException: Argument #0 of constructor [constructor for testelombok.TestFoo, annotations: {interface java.beans.ConstructorProperties=@java.beans.ConstructorProperties(value=[x, z]), interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}] has no property name annotation; must have name when multiple-parameter constructor annotated as Creator
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addDeserializerConstructors(BasicDeserializerFactory.java:511)
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._constructDefaultValueInstantiator(BasicDeserializerFactory.java:323)
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findValueInstantiator(BasicDeserializerFactory.java:253)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:219)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:141)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:406)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:352)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
    ... 7 more

Ya intenté pinchar aleatoriamente con las anotaciones @Valuey @AllArgsConstructor, pero no pude hacerlo mejor.

Busqué en Google la excepción y encontré un informe de error antiguo en Jackson , y otro que está abierto, pero parece estar relacionado con otra cosa . Sin embargo, esto todavía no dice nada sobre qué es este error o cómo solucionarlo. Además, no pude encontrar nada útil buscando eso en otro lado.

Dado que lo que estoy tratando de hacer es un uso muy básico tanto de lombok como de jackson, parece extraño que no haya podido encontrar más información útil sobre cómo solucionar este problema. ¿Quizás me perdí algo?

Aparte de simplemente decir " no use lombok " o " no use jackson ", ¿alguien tiene alguna idea sobre cómo resolver esto?

Victor Stafusa
fuente

Respuestas:

58

Si desea POJO inmutable pero serializable json usando lombok y jackson. Use la nueva anotación de Jackson en su constructor de lomboks. Probé @JsonPOJOBuilder(withPrefix = "") esta solución y funciona muy bien. Uso de muestra

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;

@JsonDeserialize(builder = Detail.DetailBuilder.class)
@Value
@Builder
public class Detail {

    private String url;
    private String userName;
    private String password;
    private String scope;

    @JsonPOJOBuilder(withPrefix = "")
    public static class DetailBuilder {

    }
}

Si tiene demasiadas clases con @Buildery no desea que el código repetitivo anotación vacía, puede anular el interceptor de anotaciones para que esté vacíowithPrefix

mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
        @Override
        public JsonPOJOBuilder.Value findPOJOBuilderConfig(AnnotatedClass ac) {
            if (ac.hasAnnotation(JsonPOJOBuilder.class)) {//If no annotation present use default as empty prefix
                return super.findPOJOBuilderConfig(ac);
            }
            return new JsonPOJOBuilder.Value("build", "");
        }
    });

Y puede eliminar la clase de constructor vacía con @JsonPOJOBuilderanotación.

ganesan dharmalingam
fuente
Un enlace a una solución es bienvenido, pero asegúrese de que su respuesta sea útil sin él: agregue contexto alrededor del enlace para que sus compañeros usuarios tengan una idea de qué es y por qué está allí, luego cite la parte más relevante de la página. está enlazando a en caso de que la página de destino no esté disponible. Las respuestas que son poco más que un enlace pueden eliminarse.
geisterfurz007
Esta solución mantiene la inmutabilidad mientras trabaja con los problemas de deserialización basados ​​en el constructor de muerte cerebral de Jackson (el constructor de campos múltiples requiere @JsonProperty en cada argumento del constructor, en lugar de intentar algo inteligente). (Probé onConstructor _ = {@ JsonCreator} sin suerte)
JeeBee
35

Immutable + Lombok + Jackson se puede lograr de la siguiente manera:

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Value;

@Value
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
@AllArgsConstructor
public class LocationDto {

    double longitude;
    double latitude;
}

class ImmutableWithLombok {

    public static void main(String[] args) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();

        String stringJsonRepresentation = objectMapper.writeValueAsString(new LocationDto(22.11, 33.33));
        System.out.println(stringJsonRepresentation);

        LocationDto locationDto = objectMapper.readValue(stringJsonRepresentation, LocationDto.class);
        System.out.println(locationDto);
    }
}
Alex Efimov
fuente
4
¿No es AllArgsConstructorya parte de la @Valueanotación?
Zon
8
Agregar un explícito @NoArgsConstructoranula el constructor que es generado por la @Valueanotación, por lo que debe agregar el extra@AllArgsConstructor
Davio
1
La mejor solución que he encontrado, en mi opinión, sin el patrón Builder. Gracias.
Radouxca
13

Probé varios de los anteriores y todos fueron temperamentales. Lo que realmente funcionó para mí es la respuesta que encontré aquí .

en el directorio raíz de su proyecto agregue un archivo lombok.config (si aún no lo ha hecho)

lombok.config

y dentro pega esto

lombok.anyConstructor.addConstructorProperties=true

Entonces puedes definir tus pojos de la siguiente manera:

@Data
@AllArgsConstructor
public class MyPojo {

    @JsonProperty("Description")
    private String description;
    @JsonProperty("ErrorCode")
    private String errorCode;
}
Giorgos Lamprakis
fuente
2
¿Pero no es esto mutable?
vphilipnyc
8

Tuve exactamente el mismo problema, lo "resolví" agregando el suppressConstructorProperties = trueparámetro (usando su ejemplo):

@Value
@Wither
@AllArgsConstructor(suppressConstructorProperties = true)
public class TestFoo {
    @JsonProperty("xoom")
    private String x;
    private int z;
}

A Jackson aparentemente no le gusta la java.beans.ConstructorProperties anotación agregada a los constructores. El suppressConstructorProperties = trueparámetro le dice a Lombok que no lo agregue (lo hace por defecto).

Donovan Muller
fuente
7
suppressConstructorPropertiesahora está en desuso :-(
lilalinux
3
Lo cual no es un problema, ya que el nuevo valor predeterminado también lo es false.
Michael Piefel
4

@AllArgsConstructor(suppressConstructorProperties = true)es obsoleto. Definir lombok.anyConstructor.suppressConstructorProperties=true( https://projectlombok.org/features/configuration ) y cambiar la anotación lombok de POJO de @Valuea @Data+ @NoArgsConstructor+ @AllArgsConstructorfunciona para mí.

yyy
fuente
14
Esto cambia la clase para que sea mutable, lo que supongo que no es lo que OP quería
Amit Goldstein
2

Para mí funcionó cuando actualicé la versión de lombok a: 'org.projectlombok: lombok: 1.18.0'

fascynacja
fuente
2

De la respuesta de Jan Rieke

Desde lombok 1.18.4, puede configurar qué anotaciones se copian en los parámetros del constructor. Inserte esto en su lombok.config:

lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty

Luego solo agregue @JsonPropertya sus campos:

...

Necesitará una @JsonProperty en cada campo, incluso si el nombre coincide, pero es una buena práctica seguir de todos modos. También puede configurar sus campos a final público usando esto, que prefiero a los getters.

@ToString
@EqualsAndHashCode
@Wither
@AllArgsConstructor(onConstructor=@__(@JsonCreator))
public class TestFoo {
    @JsonProperty("xoom")
    public final String x;
    @JsonProperty("z")
    public final int z;
}

Sin embargo, también debería funcionar con getters (+ setters).

Samuel Mingwei
fuente
1
¡Esto resuelve mi problema! Muchas gracias! Me he quedado atascado en la integración y serialización de arranque de primavera entre JSON y POJO y aparece el error: Can not deserialize instance of java.lang.String out of START_OBJECT token... ¡Y esto lo solucionó! Necesito tener esa @JsonPropertyanotación para cada parámetro de constructor y esa adición al @AllArgsConstructorpermite a lombok instrumentar eso.
Mark
1

Puedes hacer que Jackson juegue con casi cualquier cosa si usas su patrón de "mezcla" . Básicamente, le brinda una forma de agregar anotaciones de Jackson a una clase existente sin modificar esa clase. Me inclino a recomendarlo aquí en lugar de una solución de Lombok porque esto resuelve un problema que Jackson tiene con una función de Jackson, por lo que es más probable que funcione a largo plazo.

David Ehrmann
fuente
1

Tengo todas mis clases anotadas así:

@JsonAutoDetect(fieldVisibility = Visibility.ANY)
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
@Data
@Accessors(fluent = true)
@NoArgsConstructor
@AllArgsConstructor

Funcionó con todas las versiones de Lombok y Jackson durante, al menos, un par de años.

Ejemplo:

@JsonAutoDetect(fieldVisibility = Visibility.ANY)
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
@Data
@Accessors(fluent = true)
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    String id;
    String first;
    String last;
}

Y eso es. Lombok y Jackson juegan juntos como un encanto.

dónde_
fuente
9
Esta es una clase mutable.
ŁukaszG
0
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
public class Person {
   String id;
   String first;
   String last;
}

Adicional a la Clase de Datos, se debe configurar correctamente el ObjectMapper. En este caso, está funcionando bien con una configuración de ParameterNamesModule y configurando la visibilidad de los campos y métodos de creador.

    om.registerModule(new ParameterNamesModule());
    om.setVisibility(FIELD, JsonAutoDetect.Visibility.ANY);
    om.setVisibility(CREATOR, JsonAutoDetect.Visibility.ANY);

Entonces debería funcionar como se esperaba.

Héctor Delgado
fuente
0

Tenía problemas para lograr que Lombok no agregara la ConstructorProperiesanotación, así que fui por el otro lado y deshabilité a Jackson para que no mirara esa anotación.

El culpable es JacksonAnnotationIntrospector.findCreatorAnnotation . Darse cuenta:

if (_cfgConstructorPropertiesImpliesCreator
            && config.isEnabled(MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES)

También observe JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreator :

public JacksonAnnotationIntrospector setConstructorPropertiesImpliesCreator(boolean b)
{
    _cfgConstructorPropertiesImpliesCreator = b;
    return this;
}

Así que dos opciones, o bien establecen el MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIESque falsa o crean un JacksonAnnotationIntrospectorconjunto setConstructorPropertiesImpliesCreatorde falsey establecer esta AnnotationIntrospectoren el ObjectMappermedio de ObjectMapper.setAnnotationIntrospector .

Observe un par de cosas, estoy usando Jackson 2.8.10 y en esa versión MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIESno existe. No estoy seguro en qué versión de Jackson se agregó. Entonces, si no está allí, use el JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreatormecanismo.

Juan B
fuente
0

También debe tener este módulo. https://github.com/FasterXML/jackson-modules-java8

luego active el indicador -parameters para su compilador.

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
Matt Broekhuis
fuente
0

También luché con esto por un momento. Pero mirando la documentación aquí , puedo ver que el parámetro de anotación onConstructor se considera experimental y no es compatible con mi IDE (STS 4). Según la documentación de Jackson, los miembros privados no se (des) serializan de forma predeterminada. Hay formas rápidas de resolver esto.

Agregue la anotación JsonAutoDetect y configúrela adecuadamente para detectar miembros protegidos / privados. Esto es conveniente para los DTO

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class SomeClass

Agregue una función de fábrica con la anotación @JsonCreator, esto funciona mejor si necesita alguna validación de objeto o transformaciones adicionales.

public class SomeClass {

   // some code here

   @JsonCreator
   public static SomeClass factory(/* params here dressing them in @JsonProperty annotations*/) {
      return new SomeClass();
   }
}

Por supuesto, también puede agregar manualmente el constructor en usted mismo.

usuario3416682
fuente
-1

Tuve un problema diferente y fue con los tipos primitivos booleanos.

private boolean isAggregate;

Estaba arrojando el siguiente error como resultado

Exception: Unrecognized field "isAggregate" (class 

Lambok se convierte isAggregateen isAggregate()un captador que convierte la propiedad internamente en lombok como en su aggregatelugar isAggregate. A la biblioteca de Jackson no le gusta y necesita isAggregatepropiedades.

Actualicé el booleano primitivo a Wrapper Boolean para solucionar este problema. Hay otras opciones para usted si se trata de booleantipos, consulte la referencia a continuación.

Sol:

private Boolean isAggregate;

ref: https://www.baeldung.com/lombok-getter-boolean

Zeus
fuente
¿Intentó usar aggregate en lugar de isAggregate con el tipo primitivo? Creo que también puede resolver el error. Consulte también este stackoverflow.com/a/42620096/6332074
Muhammad Waqas Dilawar