Não consigo fazer Jackson e Lombok trabalharem juntos

97

Estou experimentando combinar Jackson e Lombok. Essas são minhas aulas:

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

}

Esses são os JARs que estou adicionando ao classpth:

Estou compilando com o Netbeans (não acho que isso seja realmente relevante, mas estou relatando isso de qualquer maneira para torná-lo reproduzível perfeita e fielmente). Os cinco JARs acima são mantidos em uma pasta chamada " lib" dentro da pasta do projeto (junto com " src", " nbproject", " test" e " build"). Eu os adicionei ao Netbeans através do botão " Adicionar JAR / Pasta " nas propriedades do projeto e eles estão listados na ordem exata da lista acima. O projeto é um projeto do tipo "aplicativo Java" padrão.

Além disso, o projeto Netbeans está configurado para " NÃO compilar ao salvar ", " gerar informações de depuração ", " relatar APIs obsoletas ", " rastrear dependências de java ", " processamento de anotação de ativação " e " processamento de anotação de ativação no editor ". Nenhum processador de anotação ou opção de processamento de anotação é explicitamente configurado no Netbeans. Além disso, a -Xlint:allopção de linha de comando " " é passada na linha de comando do compilador e o compilador é executado em uma VM externa.

A versão do meu javac é 1.8.0_72 e a versão do meu java é 1.8.0_72-b15. Meu Netbeans é 8.1.

Meu projeto compila bem. No entanto, ele lança uma exceção em sua execução. A exceção não parece ser nada que pareça fácil ou óbvio consertável. Aqui está a saída, incluindo o rastreamento de pilha:

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

Eu já tentei cutucar aleatoriamente com as anotações @Valuee @AllArgsConstructor, mas não consegui fazer melhor.

Eu pesquisei a exceção e encontrei um relatório de bug antigo no jackson , e outro que está aberto, mas parece estar relacionado a outra coisa . No entanto, isso ainda não diz nada sobre o que é esse bug ou como corrigi-lo. Além disso, não consegui encontrar nada útil procurando em outro lugar.

Visto que o que estou tentando fazer é o uso muito básico de lombok e jackson, parece estranho que eu não tenha conseguido encontrar mais informações úteis sobre como solucionar esse problema. Talvez eu tenha perdido alguma coisa?

Além de dizer " não use lombok " ou " não use jackson ", alguém tem alguma idéia de como resolver isso?

Victor Stafusa
fonte

Respostas:

59

Se você quiser imutável, mas um POJO serializável json usando lombok e jackson. Use jacksons new annotation em seu lomboks builder @JsonPOJOBuilder(withPrefix = "") Eu tentei esta solução e funciona muito bem. Uso de amostra

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 {

    }
}

Se você tiver muitas classes @Buildere não quiser que a anotação vazia do código padrão, você pode substituir o interceptor da anotação para que fiquewithPrefix

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

E você pode remover a classe de construtor vazia com @JsonPOJOBuilderanotação.

ganesan dharmalingam
fonte
Um link para uma solução é bem-vindo, mas certifique-se de que sua resposta seja útil sem ele: adicione contexto ao link para que seus outros usuários tenham uma ideia do que ele é e por que está lá, depois cite a parte mais relevante da página que você ' novo link para caso a página de destino não esteja disponível. Respostas que são pouco mais do que um link podem ser excluídas.
geisterfurz007
Esta solução mantém a imutabilidade enquanto trabalha em torno dos problemas de desserialização com base no construtor de morte cerebral de Jackson (o construtor de vários campos requer @JsonProperty em cada argumento do construtor, em vez de tentar algo inteligente). (Eu tentei noConstructor _ = {@ JsonCreator} sem sorte)
JeeBee
38

Immutable + Lombok + Jackson pode ser alcançado da próxima maneira:

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
fonte
4
AllArgsConstructorJá não faz parte da @Valueanotação?
Zon
8
Adicionar um explícito @NoArgsConstructorsubstitui o construtor que é gerado pela @Valueanotação, então você tem que adicionar o extra@AllArgsConstructor
Davio
1
Melhor solução que encontrei - na minha opinião - sem o padrão Builder. Obrigado.
Radouxca
16

Tentei várias das opções acima e todas foram temperamentais. O que realmente funcionou para mim foi a resposta que encontrei aqui .

no diretório raiz do seu projeto, adicione um arquivo lombok.config (se ainda não o fez)

lombok.config

e dentro cole isso

lombok.anyConstructor.addConstructorProperties=true

Em seguida, você pode definir seus pojos da seguinte forma:

@Data
@AllArgsConstructor
public class MyPojo {

    @JsonProperty("Description")
    private String description;
    @JsonProperty("ErrorCode")
    private String errorCode;
}
Giorgos Lamprakis
fonte
2
Mas isso não é mutável?
vphilipnyc
para ser imutável, basta alterar os dados por Getter e Builder ou RequiredArgsConstructor e marcar todos os campos como finais
nekperu15739
alguma ideia sobre por que isso não está funcionando no meu caso? Eu tenho um aplicativo de inicialização do Spring e coloquei lombok.config dentro do meu src, mas sua solução ainda não está funcionando.
Thanos M
8

Eu tive exatamente o mesmo problema, "resolvi" adicionando o suppressConstructorProperties = trueparâmetro (usando seu exemplo):

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

Jackson aparentemente não gosta da java.beans.ConstructorProperties anotação adicionada aos construtores. O suppressConstructorProperties = trueparâmetro diz ao Lombok para não adicioná-lo (ele o faz por padrão).

Donovan Muller
fonte
7
suppressConstructorPropertiesagora está obsoleto :-(
lilalinux
3
O que não é um problema, já que o novo padrão também é false.
Michael Piefel,
4

@AllArgsConstructor(suppressConstructorProperties = true)está obsoleto. Definir lombok.anyConstructor.suppressConstructorProperties=true( https://projectlombok.org/features/configuration ) e alterar a anotação lombok do POJO de @Valuepara @Data+ @NoArgsConstructor+ @AllArgsConstructorfunciona para mim.

yyy
fonte
14
Isso muda a classe para ser mutável, o que eu presumo que não seja o que OP queria
Amit Goldstein
3

Da resposta de Jan Rieke

Desde o lombok 1.18.4, você pode configurar quais anotações são copiadas para os parâmetros do construtor. Insira isso em lombok.config:

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

Depois é só adicionar @JsonProperty aos seus campos:

...

Você precisará de @JsonProperty em cada campo, mesmo se o nome corresponder, mas essa é uma boa prática a ser seguida de qualquer maneira. Você também pode definir seus campos para público final usando isso, que eu prefiro a getters.

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

Ele também deve funcionar com getters (+ setters).

Mingwei Samuel
fonte
1
Isso resolve meu problema! Muito obrigado! Eu estive preso na integração e serialização do spring-boot entre JSON e POJOs e recebo o erro: Can not deserialize instance of java.lang.String out of START_OBJECT token... E isso corrigiu! Preciso ter essa @JsonPropertyanotação para cada parâmetro do construtor e essa adição ao @AllArgsConstructorlombok permite que o instrumentem.
Marcos
2

Para mim, funcionou quando atualizei a versão do lombok para: 'org.projectlombok: lombok: 1.18.0'

fascynacja
fonte
1

Você pode fazer Jackson tocar com quase qualquer coisa se usar seu padrão de "mixagem" . Basicamente, oferece uma maneira de adicionar anotações de Jackson a uma classe existente sem realmente modificar essa classe. Estou inclinado a recomendá-lo aqui, em vez de uma solução Lombok, porque isso resolve um problema que Jackson está tendo com um recurso de Jackson, então é mais provável que funcione a longo prazo.

David Ehrmann
fonte
1

Todas as minhas aulas foram anotadas assim:

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

Funcionou com todas as versões de Lombok e Jackson por, pelo menos, alguns anos.

Exemplo:

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

E é isso. Lombok e Jackson brincam juntos como um encanto.

Onde_
fonte
9
Esta é uma classe mutável.
ŁukaszG
0
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
public class Person {
   String id;
   String first;
   String last;
}

Adicional à Classe de Dados, deve ser configurado corretamente o ObjectMapper. Neste caso, está funcionando bem com a configuração ParameterNamesModule, e definindo a visibilidade dos Campos e Métodos do Criador

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

Então, ele deve funcionar conforme o esperado.

Hector Delgado
fonte
0

Eu estava tendo problemas para fazer o Lombok não adicionar a ConstructorProperiesanotação, então fui para o outro lado e desabilitei Jackson de olhar para aquela anotação.

O culpado é JacksonAnnotationIntrospector.findCreatorAnnotation . Aviso prévio:

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

Observe também JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreator :

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

Então duas opções, ou definir a MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIEScomo falsa ou criar um JacksonAnnotationIntrospectorconjunto setConstructorPropertiesImpliesCreatorde falsee definir esta AnnotationIntrospectornaObjectMapper via ObjectMapper.setAnnotationIntrospector .

Observe algumas coisas, estou usando o Jackson 2.8.10 e essa versão MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIESnão existe. Não tenho certeza em qual versão de Jackson foi adicionado. Portanto, se não estiver lá, use o JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreatormecanismo.

John B
fonte
0

Você precisa ter este módulo também. https://github.com/FasterXML/jackson-modules-java8

em seguida, ative a sinalização -parameters para seu 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
fonte
0

Eu lutei com isso por um momento também. Mas olhando a documentação aqui , posso ver que o parâmetro de anotação onConstructor é considerado experimental e não é bem suportado em meu IDE (STS 4). De acordo com a documentação de Jackson, os membros privados não são (des) serializados por padrão. Existem maneiras rápidas de resolver isso.

Adicione a anotação JsonAutoDetect e defina-a apropriadamente para detectar membros protegidos / privados. Isso é conveniente para DTOs

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

Adicione uma função de fábrica com a anotação @JsonCreator, isso funciona melhor se você precisar de alguma validação de objeto ou transformações adicionais.

public class SomeClass {

   // some code here

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

Claro que você também pode adicionar manualmente o construtor em você mesmo.

user3416682
fonte
-1

Eu tive um problema diferente e foi com os tipos primitivos booleanos.

private boolean isAggregate;

Ele estava gerando o seguinte erro como resultado

Exception: Unrecognized field "isAggregate" (class 

Convertidos Lambok isAggregatea isAggregate()como um getter tornando a propriedade internamente para Lombok como aggregatevez isAggregate. A biblioteca Jackson não gosta e, isAggregateem vez disso , precisa de propriedade.

Atualizei o booleano primitivo para Wrapper Boolean para contornar esse problema. Existem outras opções para você se estiver lidando com booleantipos, consulte a referência abaixo.

Sol:

private Boolean isAggregate;

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

Zeus
fonte
Você tentou usar agregado em vez de isAggregate com tipo primitivo? Eu acredito que pode resolver o erro também. Consulte também stackoverflow.com/a/42620096/6332074
Muhammad Waqas Dilawar