【问题标题】:Deserialize ImmutableList using Jackson & Lombok使用 Jackson 和 Lombok 反序列化 ImmutableList
【发布时间】:2020-09-06 01:34:36
【问题描述】:

我正在尝试使以下示例正常工作:

    @Value
    @JsonDeserialize(builder = SomeClass.Builder.class)
    @Builder(builderClassName = "Builder")
    public static class SomeClass {
        @Wither
        ImmutableList<String> words;

        @JsonPOJOBuilder(withPrefix = "")
        public static class Builder {
        }
    }

    @Test
    @SneakyThrows
    public void serializeTest() {
        ObjectMapper objectMapper = new ObjectMapper();
        SomeClass someClass = SomeClass.builder()
            .words(ImmutableList.of("word1", "word2", "word3"))
            .build();
        String jsonString = objectMapper.writeValueAsString(someClass);
        log.info("serialized: {}", jsonString);
        SomeClass newSomeClass = objectMapper.readValue(jsonString, SomeClass.class);
        log.info("done");
        newSomeClass.words.forEach(w -> log.info("word {}", w));
    }

但是它失败了

Caused by: java.lang.IllegalArgumentException: Cannot find a deserializer for non-concrete Collection type [collection type; class com.google.common.collect.ImmutableList, contains [simple type, class java.lang.String]]
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.createCollectionDeserializer(BasicDeserializerFactory.java:1205)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:399)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:349)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)

来自this answer,我尝试了类似:

    @Value
    @JsonDeserialize(builder = SomeClass.Builder.class)
    @Builder(builderClassName = "Builder")
    public static class SomeClass {
        @Wither
        @JsonDeserialize(using = ImmutableListDeserializer.class)
        ImmutableList<String> words;

        @JsonPOJOBuilder(withPrefix = "")
        public static class Builder {
        }
    }

    @Test
    @SneakyThrows
    public void serializeTest() {
        ObjectMapper objectMapper = new ObjectMapper();
        SomeClass someClass = SomeClass.builder()
            .words(ImmutableList.of("word1", "word2", "word3"))
            .build();
        String jsonString = objectMapper.writeValueAsString(someClass);
        log.info("serialized: {}", jsonString);
        SomeClass newSomeClass = objectMapper.readValue(jsonString, SomeClass.class);
        log.info("done");
        newSomeClass.words.forEach(w -> log.info("word {}", w));
    }

但是失败了:

Caused by: java.lang.IllegalArgumentException: Cannot find a deserializer for non-concrete Collection type [collection type; class com.google.common.collect.ImmutableList, contains [simple type, class java.lang.String]]

由于我正在处理的项目的限制,我无法修改对象映射器。 所以我不能简单地这样做:

objectMapper.registerModule(new GuavaModule());

这本来可以的。

还有其他直接的方法可以让简单的 ImmutableList 反序列化工作吗?

编辑:我设法得到了一个不同的错误:

    @Value
    @JsonDeserialize(builder = SomeClass.Builder.class)
    @Builder(builderClassName = "Builder")
    public static class SomeClass {
        @Wither
        ImmutableList<String> strings;

        @JsonPOJOBuilder(withPrefix = "")
        public static class Builder {
            @JsonDeserialize(using = ImmutableListDeserializer.class)
            public Builder strings(ImmutableList<String> strings) {
                this.strings = strings;
                return this;
            }
        }
    }

    @Test
    @SneakyThrows
    public void serializeTest() {
        ObjectMapper objectMapper = new ObjectMapper();
        SomeClass someClass = SomeClass.builder()
            .strings(ImmutableList.of("word1", "word2", "word3"))
            .build();
        String jsonString = objectMapper.writeValueAsString(someClass);
        log.info("serialized: {}", jsonString);
        SomeClass newSomeClass = objectMapper.readValue(jsonString, SomeClass.class);
        log.info("done");
        newSomeClass.strings.forEach(w -> log.info("word {}", w));
    }

抛出:

Caused by: java.lang.IllegalArgumentException: Class com.fasterxml.jackson.datatype.guava.deser.ImmutableListDeserializer has no default (no arg) constructor

如果我可以为 ImmutableListDeserializer 或类似的东西构建一个无参数构造函数,这可能更容易解决

【问题讨论】:

    标签: java java-8 jackson deserialization lombok


    【解决方案1】:

    最后不得不编写一个自定义序列化程序。如果您有更优雅的答案,请告诉我。以下是可能最终出现在这里的人的工作示例:

        @Value
        @JsonDeserialize(builder = SomeClass.Builder.class)
        @Builder(builderClassName = "Builder")
        public static class SomeClass {
            @Wither
            ImmutableList<String> strings;
    
            @JsonPOJOBuilder(withPrefix = "")
            public static class Builder {
                @JsonDeserialize(using = CustomDeserializer.class)
                public Builder strings(ImmutableList<String> strings) {
                    this.strings = strings;
                    return this;
                }
            }
        }
    
        public static class CustomDeserializer extends StdDeserializer<ImmutableList<String>> {
            public CustomDeserializer() {
                super(ImmutableList.class);
            }
    
            @Override
            public ImmutableList<String> deserialize(JsonParser parser, DeserializationContext deserializer) throws IOException {
                ImmutableList.Builder<String> immutableListBuilder = ImmutableList.builder();
                while (!parser.isClosed()) {
                    JsonToken jsonToken = parser.nextToken();
                    if (JsonToken.VALUE_STRING.equals(jsonToken)) {
                        final String fieldValue = parser.getValueAsString();
                        immutableListBuilder.add(fieldValue);
                    } else if (JsonToken.END_ARRAY.equals(jsonToken)) {
                        break;
                    }
                }
                return immutableListBuilder.build();
            }
        }
    
        @Test
        @SneakyThrows
        public void serializeTest() {
            ObjectMapper objectMapper = new ObjectMapper();
            SomeClass someClass = SomeClass.builder()
                .strings(ImmutableList.of("word1", "word2", "word3"))
                .build();
            String jsonString = objectMapper.writeValueAsString(someClass);
            log.info("serialized: {}", jsonString);
            SomeClass newSomeClass = objectMapper.readValue(jsonString, SomeClass.class);
            log.info("done");
            newSomeClass.strings.forEach(w -> log.info("word {}", w));
        }
    

    我发现了一篇非常好的文章:http://tutorials.jenkov.com/java-json/jackson-objectmapper.html

    【讨论】:

    • 请在下面查看我的解决方案。
    【解决方案2】:
    1. 让 IDE 生成构造函数
    2. 在构造函数中添加@JsonCreator
    3. 添加ParameterNamesModule
    4. GuavaModule 添加到映射器。
    5. 使用-parameters 标志编译
    
    import org.junit.jupiter.api.Test;
    
    import com.fasterxml.jackson.annotation.JsonCreator;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.datatype.guava.GuavaModule;
    import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
    import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
    import com.google.common.collect.ImmutableList;
    
    import lombok.AccessLevel;
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Singular;
    import lombok.Value;
    import lombok.With;
    import lombok.extern.log4j.Log4j2;
    
    @SuppressWarnings("javadoc")
    @Log4j2
    public class Q61900327 {
    
        /**
         * We add the ParameterNamesModule so Jackson can use the constructor arguments to
         * properly map the fields.
         */
        static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new GuavaModule())
                .registerModule(new ParameterNamesModule())
                .registerModule(new Jdk8Module());
    
        /**
         * This solution works at the cost of having the IDE generate the constructor.
         */
        static class ExplicitConstructorSolution {
    
            @Value
            @Builder(toBuilder = true)
            public static class SomeClass {
    
                @With
                @Singular
                private ImmutableList<String> words;
    
                @JsonCreator
                public SomeClass(ImmutableList<String> words) {
                    super();
                    this.words = words;
                }
            }
    
            @Test
            void serializeTest() throws JsonProcessingException {
    
    
                var someClass = SomeClass.builder()
                        .word("word1")
                        .word("word2")
                        .word("word3")
                        .build();
    
                try {
                    var jsonString = OBJECT_MAPPER.writeValueAsString(someClass);
                    log.info("serialized: {}", jsonString);
                    var newSomeClass = OBJECT_MAPPER.readValue(jsonString, SomeClass.class);
                    newSomeClass.words.forEach(w -> log.info("word {}", w));
                }
                catch (JsonProcessingException e) {
                    log.error("someClass could not roundtrip.", e);
                    throw e;
                }
            }
        }
    
        /**
         * Note this solution looks great but unfortunately will cause javadoc to fail with:
         * <p>
         * {@code
         * error: cannot find symbol  [ERROR] @AllArgsConstructor(access = AccessLevel.PUBLIC, onConstructor_ =
         * { @JsonCreator })}
         * <p>
         * @see <a href="https://github.com/rzwitserloot/lombok/issues/2137">lombok #2137</a>
         *
         */
        static class OnConstructorSolution {
    
            @Value
            @AllArgsConstructor(access = AccessLevel.PUBLIC, onConstructor_ = { @JsonCreator })
            @Builder(toBuilder = true)
            public static class SomeClass {
    
                @With
                @Singular
                private ImmutableList<String> words;
            }
    
            @Test
            void serializeTest() throws JsonProcessingException {
    
                var someClass = SomeClass.builder()
                        .word("word1")
                        .word("word2")
                        .word("word3")
                        .build();
    
                try {
                    var jsonString = OBJECT_MAPPER.writeValueAsString(someClass);
                    log.info("serialized: {}", jsonString);
                    var newSomeClass = OBJECT_MAPPER.readValue(jsonString, SomeClass.class);
                    newSomeClass.words.forEach(w -> log.info("word {}", w));
                }
                catch (JsonProcessingException e) {
                    log.error("someClass could not roundtrip.", e);
                    throw e;
                }
            }
        }
    
    }
    
    
    

    pom:

    
    <?xml version="1.0" encoding="UTF-8" ?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>io.jeffmaxwell.stackoverflow</groupId>
      <artifactId>stackoverflow</artifactId>
      <version>0.0.1-SNAPSHOT</version>
    
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    
        <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
        <maven.compiler.source>14</maven.compiler.source>
        <maven.compiler.target>14</maven.compiler.target>
        <maven.compiler.release>14</maven.compiler.release>
        <maven.compiler.parameters>true</maven.compiler.parameters>
        <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
        <maven.compiler.showWarnings>true</maven.compiler.showWarnings>
        <maven.compiler.verbose>true</maven.compiler.verbose>
    
        <maven-dependency-plugin.version>3.1.2</maven-dependency-plugin.version>
    
        <guava.version>29.0-jre</guava.version>
        <junit.version>5.6.2</junit.version>
        <log4j2.version>2.13.3</log4j2.version>
        <jackson.version>2.11.0</jackson.version>
        <lombok.version>1.18.12</lombok.version>
      </properties>
    
      <build>
        <pluginManagement>
          <plugins>
            <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>${maven-compiler-plugin.version}</version>
            </plugin>
    
            <plugin>
              <artifactId>maven-dependency-plugin</artifactId>
              <version>${maven-dependency-plugin.version}</version>
              <configuration>
                <outputXML>true</outputXML>
                <verbose>true</verbose>
              </configuration>
            </plugin>
          </plugins>
        </pluginManagement>
      </build>
    
      <dependencyManagement>
        <dependencies>
          <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
          </dependency>
    
          <dependency>
            <groupId>org.junit</groupId>
            <artifactId>junit-bom</artifactId>
            <version>${junit.version}</version>
            <type>pom</type>
            <scope>import</scope>
          </dependency>
    
          <dependency>
            <groupId>com.fasterxml.jackson</groupId>
            <artifactId>jackson-bom</artifactId>
            <version>${jackson.version}</version>
            <type>pom</type>
            <scope>import</scope>
          </dependency>
    
          <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-bom</artifactId>
            <version>${log4j2.version}</version>
            <type>pom</type>
            <scope>import</scope>
          </dependency>
    
          <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
          </dependency>
        </dependencies>
      </dependencyManagement>
    
      <dependencies>
        <dependency>
          <groupId>com.google.guava</groupId>
          <artifactId>guava</artifactId>
        </dependency>
    
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <scope>provided</scope>
        </dependency>
    
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-annotations</artifactId>
        </dependency>
    
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-core</artifactId>
        </dependency>
    
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
        </dependency>
    
        <dependency>
          <groupId>com.fasterxml.jackson.datatype</groupId>
          <artifactId>jackson-datatype-guava</artifactId>
        </dependency>
    
        <dependency>
          <groupId>com.fasterxml.jackson.datatype</groupId>
          <artifactId>jackson-datatype-jdk8</artifactId>
        </dependency>
    
        <dependency>
          <groupId>com.fasterxml.jackson.module</groupId>
          <artifactId>jackson-module-parameter-names</artifactId>
        </dependency>
    
        <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-api</artifactId>
        </dependency>
    
        <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-core</artifactId>
          <scope>runtime</scope>
        </dependency>
    
        <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-1.2-api</artifactId>
          <scope>runtime</scope>
        </dependency>
    
        <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-jcl</artifactId>
          <scope>runtime</scope>
        </dependency>
    
        <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-slf4j-impl</artifactId>
          <scope>runtime</scope>
        </dependency>
    
        <dependency>
          <groupId>org.junit.jupiter</groupId>
          <artifactId>junit-jupiter-api</artifactId>
        </dependency>
    
        <dependency>
          <groupId>org.junit.jupiter</groupId>
          <artifactId>junit-jupiter-engine</artifactId>
          <scope>runtime</scope>
        </dependency>
      </dependencies>
    </project>
    
    

    【讨论】:

    • 您好,首先非常感谢您为此付出的努力!尤其是用于分享包含 pom 的完整示例。不过,我在问题中也注意到了一个问题:我不能使用“objectMapper.registerModule(new GuavaModule());”,因为我发布的示例是一个更大项目的简化版本,其中一个依赖项是使用他们自己的 objectMapper 映射我无法修改的输入对象:/
    • 那个objectmapper是否在spring boot中配置过?
    • 你还有添加依赖的能力吗?
    • 不幸的是,两者都没有:/ 我们使用 Guice 进行依赖注入。这是一个大项目,对依赖做任意更改并不容易。
    猜你喜欢
    • 2021-05-29
    • 2011-12-04
    • 2021-06-29
    • 2019-06-18
    • 1970-01-01
    • 2020-11-28
    • 2019-06-23
    • 2016-12-11
    • 1970-01-01
    相关资源
    最近更新 更多