代码之家  ›  专栏  ›  技术社区  ›  ozgeneral

使用Jackson Lombok序列化ImmutableList

  •  0
  • ozgeneral  · 技术社区  · 5 年前

    我试图让以下示例工作:

        @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构建一个无参数构造函数,或者类似的东西,也许这更容易解决

    0 回复  |  直到 5 年前
        1
  •  1
  •   Jeff    5 年前
    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>
    
    
        2
  •  0
  •   ozgeneral    5 年前

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

        @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