ããã¯ããªã«ãããããŠæžãããã®ïŒ
Jacksonã䜿ã£ãŠãªããžã§ã¯ããJSONã«ã·ãªã¢ã©ã€ãºããéã«ãnull
ã®ããããã£ã®åºåã¯æå¶ããããªãããšããããŸãã
JSONã«ããæã«ã以äžã®ãããªç¶æ ã
{ "property1": "value1", "property2": null }
ããããããšãã話ã§ããã
{ "property1": "value1" }
ãã®ããæ¹ãå¿ããŠæ¯å調ã¹ãŠããæ°ãããã®ã§ãã¡ã¢ããŠãããããªãšã
ãµã€ãã«Jacksonã䜿ã£ãå ŽåãSpring Bootã䜿ã£ãå ŽåãQuarkusã䜿ã£ãå Žåã§ããããã¡ã¢ããŠãããŸãã
æ¹æ³
倧ããã2ã€ã®æ¹æ³ããããŸãã
- @JsonIncludeã¢ãããŒã·ã§ã³ã§Include.NON_NULLãæå®ãã
- ObjectMapper#setSerializationInclusionã§Include.NON_NULLãæå®ãã
åè ã¯å¯Ÿè±¡ãšãªãããããã£ãæã€ã¯ã©ã¹ããããã¯ããããã£èªäœã«ä»äžããåŸè ã¯ããã©ã«ãã®èšå®ãšããŠæ¯ãèããŸãã
Spring BootãQuarkusã¯ãObjectMapper#setSerializationInclusion
ãèšå®ãã¡ã€ã«ã§è¡ããããã«ãªã£ãŠããŸãã
- Spring Boot
- Quarkus
ä»åã¯ããããããJackson 2.14.1ã§è©ŠããŠãããããªãšæããŸããSpring Boot 3.0.1ãQuarkus 2.15.3.Finalã§ãå«ãŸããŠããJacksonã®
ããŒãžã§ã³ã¯2.14.1ã«ãªã£ãŠããŸãã
ç°å¢
ä»åã®ç°å¢ã¯ããã¡ãã
$ java --version openjdk 17.0.5 2022-10-18 OpenJDK Runtime Environment (build 17.0.5+8-Ubuntu-2ubuntu122.04) OpenJDK 64-Bit Server VM (build 17.0.5+8-Ubuntu-2ubuntu122.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.7 (b89d5959fcde851dcb1c8946a785a163f14e1e29) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 17.0.5, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.15.0-58-generic", arch: "amd64", family: "unix"
ãé¡
æžç±ããé¡ã«ããŠã以äžã®ãããªã¯ã©ã¹ã察象ã«ããããšæããŸãã
src/main/java/org/littlewings/jackson/nonnull/Book.java
package org.littlewings.jackson.nonnull; import java.util.Arrays; import java.util.List; public class Book { String isbn; String title; Integer price; Category category; List<Category> categories; public static Book create(String isbn, String title, Integer price, Category... categories) { Book book = new Book(); book.setIsbn(isbn); book.setTitle(title); book.setPrice(price); book.setCategory(categories.length > 0 ? categories[0] : null); book.setCategories(categories != null ? Arrays.asList(categories) : null); return book; } // getterïŒsetterã¯çç¥ }
ãã¹ãããããããã£ããã³ã³ã¬ã¯ã·ã§ã³ã«ã€ããŠã確èªãããã£ãã®ã§ãã«ããŽãªãŒãæã€ããšã«ããŠãã¡ãã£ãšãããã«ããã§ãã
ã«ããŽãªãŒã®æåã®ã²ãšã€ãåç¬ã®ããããã£ã§æã€ããšã«ããŸããã
Category category; List<Category> categories;
ã«ããŽãªãŒåŽã¯ãããªæãã«ããŸãã
src/main/java/org/littlewings/jackson/nonnull/Category.java
package org.littlewings.jackson.nonnull; public class Category { Integer id; String name; public static Category create(Integer id, String name) { Category category = new Category(); category.setId(id); category.setName(name); return category; } // getterïŒsetterã¯çç¥ }
Jacksonåäœã§è©ŠããŠã¿ã
ãŸãã¯ãJacksonåäœã§è©ŠããŸãã
確èªã¯ãã¹ãã³ãŒãã§è¡ããŸãããã
æºå
MavenäŸåé¢ä¿ãªã©ã
<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.14.1</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.24.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> </plugin> </plugins> </build>
ãã¹ãã³ãŒãã®é圢ããããªæãã§äœæã
src/test/java/org/littlewings/jackson/nonnull/JacksonIncludeNonNullTest.java
package org.littlewings.jackson.nonnull; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import static org.assertj.core.api.Assertions.assertThat; public class JacksonIncludeNonNullTest { // ããã«ãã¹ããæžãïŒïŒ }
ãŸãã¯ãµã€ãã«
ãšããããããã¹ãŠã®ããããã£ãénull
ã«ãªãããã«ããŠã¿ãŸãã
@Test void simply(TestInfo testInfo) throws JsonProcessingException { Book book = Book.create( " 978-4621303252", "Effective Java 第3ç", 4400, Category.create(1, "java"), Category.create(2, "programming") ); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(book); System.out.printf("%s: json = %s%n", testInfo.getDisplayName(), json); assertThat(json) .isEqualTo("{\"isbn\":\" 978-4621303252\",\"title\":\"Effective Java 第3ç\",\"price\":4400,\"category\":{\"id\":1,\"name\":\"java\"},\"categories\":[{\"id\":1,\"name\":\"java\"},{\"id\":2,\"name\":\"programming\"}]}"); }
System.out.printf
ããŠããéšåã®çµæã
simply(TestInfo): json = {"isbn":" 978-4621303252","title":"Effective Java 第3ç","price":4400,"category":{"id":1,"name":"java"},"categories":[{"id":1,"name":"java"},{"id":2,"name":"programming"}]}
æŽåœ¢åŸã
{ "isbn": " 978-4621303252", "title": "Effective Java 第3ç", "price": 4400, "category": { "id": 1, "name": "java" }, "categories": [ { "id": 1, "name": "java" }, { "id": 2, "name": "programming" } ] }
nullãå«ããŠã¿ã
次ã«ãnull
ãå«ããŠã¿ãŸãã
@Test void withNull(TestInfo testInfo) throws JsonProcessingException { Book book1 = Book.create( " 978-4621303252", null, null, Category.create(null, "java"), Category.create(2, null) ); Book book2 = Book.create( " 978-4621303252", null, null ); ObjectMapper mapper = new ObjectMapper(); String json1 = mapper.writeValueAsString(book1); String json2 = mapper.writeValueAsString(book2); System.out.printf("%s: json1 = %s%n", testInfo.getDisplayName(), json1); assertThat(json1) .isEqualTo("{\"isbn\":\" 978-4621303252\",\"title\":null,\"price\":null,\"category\":{\"id\":null,\"name\":\"java\"},\"categories\":[{\"id\":null,\"name\":\"java\"},{\"id\":2,\"name\":null}]}"); System.out.printf("%s: json2 = %s%n", testInfo.getDisplayName(), json2); assertThat(json2) .isEqualTo("{\"isbn\":\" 978-4621303252\",\"title\":null,\"price\":null,\"category\":null,\"categories\":[]}"); }
é©åœã«ããã€ãããããã£ãnull
ã«ããŠãããŸãã
Book book1 = Book.create( " 978-4621303252", null, null, Category.create(null, "java"), Category.create(2, null) ); Book book2 = Book.create( " 978-4621303252", null, null );
æšæºåºåã®æžãåºãçµæã
withNull(TestInfo): json1 = {"isbn":" 978-4621303252","title":null,"price":null,"category":{"id":null,"name":"java"},"categories":[{"id":null,"name":"java"},{"id":2,"name":null}]} withNull(TestInfo): json2 = {"isbn":" 978-4621303252","title":null,"price":null,"category":null,"categories":[]}
æŽåœ¢åŸã
## json1 { "isbn": " 978-4621303252", "title": null, "price": null, "category": { "id": null, "name": "java" }, "categories": [ { "id": null, "name": "java" }, { "id": 2, "name": null } ] } ## json2 { "isbn": " 978-4621303252", "title": null, "price": null, "category": null, "categories": [] }
null
ããã®ãŸãŸåºåãããŸããããªããã³ã¬ã¯ã·ã§ã³ã«null
ãæå®ãããšç©ºã®ã³ã¬ã¯ã·ã§ã³ã«ãªããŸãããã
@JsonInclude(JsonInclude.Include.NON_NULL)ã䜿ã
ã§ã¯ã@JsonInclude(JsonInclude.Include.NON_NULL)
ã䜿ã£ãŠã¿ãŸãããã
src/main/java/org/littlewings/jackson/nonnull/BookNonNull.java
package org.littlewings.jackson.nonnull; import java.util.Arrays; import java.util.List; import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL) public class BookNonNull { String isbn; String title; Integer price; Category category; List<Category> categories; public static BookNonNull create(String isbn, String title, Integer price, Category... categories) { BookNonNull book = new BookNonNull(); book.setIsbn(isbn); book.setTitle(title); book.setPrice(price); book.setCategory(categories.length > 0 ? categories[0] : null); book.setCategories(categories != null ? Arrays.asList(categories) : null); return book; } // getterïŒsetterã¯çç¥ }
ã¯ã©ã¹ã«@JsonInclude(JsonInclude.Include.NON_NULL)
ãä»äžã
@JsonInclude(JsonInclude.Include.NON_NULL) public class BookNonNull {
ãã¹ãã³ãŒãã
@Test void withNullExclude(TestInfo testInfo) throws JsonProcessingException { BookNonNull book1 = BookNonNull.create( " 978-4621303252", null, null, Category.create(null, "java"), Category.create(2, null) ); BookNonNull book2 = BookNonNull.create( " 978-4621303252", null, null ); ObjectMapper mapper = new ObjectMapper(); String json1 = mapper.writeValueAsString(book1); String json2 = mapper.writeValueAsString(book2); System.out.printf("%s: json1 = %s%n", testInfo.getDisplayName(), json1); assertThat(json1) .isEqualTo("{\"isbn\":\" 978-4621303252\",\"category\":{\"id\":null,\"name\":\"java\"},\"categories\":[{\"id\":null,\"name\":\"java\"},{\"id\":2,\"name\":null}]}"); System.out.printf("%s: json2 = %s%n", testInfo.getDisplayName(), json2); assertThat(json2) .isEqualTo("{\"isbn\":\" 978-4621303252\",\"categories\":[]}"); }
æšæºåºåã®æžãåºãçµæã
withNullExclude(TestInfo): json1 = {"isbn":" 978-4621303252","category":{"id":null,"name":"java"},"categories":[{"id":null,"name":"java"},{"id":2,"name":null}]} withNullExclude(TestInfo): json2 = {"isbn":" 978-4621303252","categories":[]}
æŽåœ¢åŸã
## json1 { "isbn": " 978-4621303252", "category": { "id": null, "name": "java" }, "categories": [ { "id": null, "name": "java" }, { "id": 2, "name": null } ] } ## json2 { "isbn": " 978-4621303252", "categories": [] }
ããïŒãšæããããããŸãããããã¹ãããããããã£å
ã®ããããã£ãnull
ã ã£ãããã³ã¬ã¯ã·ã§ã³å
ã®ããããã£ãnull
ã ã£ãããããš
ãã®ãŸãŸåºåãããŠããŸãã
ãã®ãããªã±ãŒã¹ã«å¯ŸããŠã¯ãåã«ããããã£ãä¿æããã¯ã©ã¹ã«@JsonInclude(JsonInclude.Include.NON_NULL)
ãä»äžããã ãã§ã¯
广ããããŸããã
ãã¹ãããã¯ã©ã¹ã«ã@JsonInclude(JsonInclude.Include.NON_NULL)ãä»äžãã
ãã¹ãããããããã£ã®null
ãé€å€ããããã«ããŠãããŸãããã
ã«ããŽãªãŒçšã®ã¯ã©ã¹ã«ã@JsonInclude(JsonInclude.Include.NON_NULL)
ãä»äžã
src/main/java/org/littlewings/jackson/nonnull/CategoryNonNull.java
package org.littlewings.jackson.nonnull; import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL) public class CategoryNonNull { Integer id; String name; public static CategoryNonNull create(Integer id, String name) { CategoryNonNull category = new CategoryNonNull(); category.setId(id); category.setName(name); return category; } // getterïŒsetterã¯çç¥ }
äœæããã¯ã©ã¹ã䜿ãããã«ãæžç±åŽã®ã¯ã©ã¹ãäœæã
src/main/java/org/littlewings/jackson/nonnull/BookNonNullNested.java
package org.littlewings.jackson.nonnull; import java.util.Arrays; import java.util.List; import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL) public class BookNonNullNested { String isbn; String title; Integer price; CategoryNonNull category; List<CategoryNonNull> categories; public static BookNonNullNested create(String isbn, String title, Integer price, CategoryNonNull... categories) { BookNonNullNested book = new BookNonNullNested(); book.setIsbn(isbn); book.setTitle(title); book.setPrice(price); book.setCategory(categories.length > 0 ? categories[0] : null); book.setCategories(categories != null ? Arrays.asList(categories) : null); return book; } // getterïŒsetterã¯çç¥ }
ãã¹ãã³ãŒãã
@Test void withNullExcludeIncludeNested(TestInfo testInfo) throws JsonProcessingException { BookNonNullNested book1 = BookNonNullNested.create( " 978-4621303252", null, null, CategoryNonNull.create(null, "java"), CategoryNonNull.create(2, null) ); BookNonNullNested book2 = BookNonNullNested.create( " 978-4621303252", null, null ); ObjectMapper mapper = new ObjectMapper(); String json1 = mapper.writeValueAsString(book1); String json2 = mapper.writeValueAsString(book2); System.out.printf("%s: json1 = %s%n", testInfo.getDisplayName(), json1); assertThat(json1) .isEqualTo("{\"isbn\":\" 978-4621303252\",\"category\":{\"name\":\"java\"},\"categories\":[{\"name\":\"java\"},{\"id\":2}]}"); System.out.printf("%s: json2 = %s%n", testInfo.getDisplayName(), json2); assertThat(json2) .isEqualTo("{\"isbn\":\" 978-4621303252\",\"categories\":[]}"); }
æšæºåºåã«æžãåºãããå 容ã
withNullExcludeIncludeNested(TestInfo): json1 = {"isbn":" 978-4621303252","category":{"name":"java"},"categories":[{"name":"java"},{"id":2}]} withNullExcludeIncludeNested(TestInfo): json2 = {"isbn":" 978-4621303252","categories":[]}
æŽåœ¢åŸã
## json1 { "isbn": " 978-4621303252", "category": { "name": "java" }, "categories": [ { "name": "java" }, { "id": 2 } ] } ## json2 { "isbn": " 978-4621303252", "categories": [] }
ããã§ããã¹ãããããããã£ãããnull
ããªããªããŸããã
空ã®ã³ã¬ã¯ã·ã§ã³ãåºåããªãããã«ãã
ãšããã§ã空ã®ã³ã¬ã¯ã·ã§ã³ãåºåããªãããã«ããå Žåã¯ã©ããããããã®ã§ããããïŒ
{ "isbn": " 978-4621303252", "categories": [] }
null
ã®åºåææ¢ãšçµã¿åããããšããããªæãã§ããã
@JsonInclude( value = JsonInclude.Include.NON_EMPTY, content = JsonInclude.Include.NON_NULL ) public class BookNonNullNested {
ç¹å®ã®ããããã£ã®ã¿èšå®ããå Žåã¯ãããªããŸãã
@JsonInclude(
value = JsonInclude.Include.NON_EMPTY,
content = JsonInclude.Include.NON_NULL
)
List<CategoryNonNull> categories;
ããã¯ãJsonInclude
ã¢ãããŒã·ã§ã³ã®Javadocã«èª¬æããããŸãã
Note that the main inclusion criteria (one annotated with value()) is checked on Java object level, for the annotated type, and NOT on JSON output -- so even with JsonInclude.Include.NON_NULL it is possible that JSON null values are output, if object reference in question is not
null
. An example is AtomicReference instance constructed to reference null value: such a value would be serialized as JSON null, and not filtered out.To base inclusion on value of contained value(s), you will typically also need to specify content() annotation; for example, specifying only value() as JsonInclude.Include.NON_EMPTY for a {link java.util.Map} would exclude Maps with no values, but would include Maps with
null
values. To exclude Map with onlynull
value, you would use both annotations like so:
public class Bean { @JsonInclude(value=Include.NON_EMPTY, content=Include.NON_NULL) public Map<String,String> entries; }
JsonInclude (Jackson-annotations 2.14.0 API)
èšå®åŸã®åºåçµæïŒæŽåœ¢åŸïŒã¯ããã§ããã
{ "isbn": " 978-4621303252" }
ããã©ã«ãã§nullãåºåããªãããã«èšå®ãã
ãããŸã§@JsonInclude
ã¢ãããŒã·ã§ã³ã§èšå®ããŠããŸããããåå¥ã«ä»äžããã®ãé¢åã ãšæãããšãããããªãšã
ããã¯ãObjectMapper#setSerializationInclusion
ã«JsonInclude.Include.NON_NULL
ãæå®ããããšã§å®çŸã§ããŸãã
@Test void defaultIncludeNonNull(TestInfo testInfo) throws JsonProcessingException { Book book1 = Book.create( " 978-4621303252", null, null, Category.create(null, "java"), Category.create(2, null) ); Book book2 = Book.create( " 978-4621303252", null, null ); ObjectMapper mapper = new ObjectMapper() .setSerializationInclusion(JsonInclude.Include.NON_NULL); String json1 = mapper.writeValueAsString(book1); String json2 = mapper.writeValueAsString(book2); System.out.printf("%s: json1 = %s%n", testInfo.getDisplayName(), json1); assertThat(json1) .isEqualTo("{\"isbn\":\" 978-4621303252\",\"category\":{\"name\":\"java\"},\"categories\":[{\"name\":\"java\"},{\"id\":2}]}"); System.out.printf("%s: json2 = %s%n", testInfo.getDisplayName(), json2); assertThat(json2) .isEqualTo("{\"isbn\":\" 978-4621303252\",\"categories\":[]}"); }
å
ã»ã©ãã£ãŠããã空ã®ã³ã¬ã¯ã·ã§ã³ã®åºåãæå¶ããå Žåã¯JsonInclude.Include.NON_EMPTY
ã远å ããŸãã
@Test void defaultIncludeNonNullAndNonEmpty(TestInfo testInfo) throws JsonProcessingException { Book book1 = Book.create( " 978-4621303252", null, null, Category.create(null, "java"), Category.create(2, null) ); Book book2 = Book.create( " 978-4621303252", null, null ); ObjectMapper mapper = new ObjectMapper() .setSerializationInclusion(JsonInclude.Include.NON_NULL) .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); String json1 = mapper.writeValueAsString(book1); String json2 = mapper.writeValueAsString(book2); System.out.printf("%s: json1 = %s%n", testInfo.getDisplayName(), json1); assertThat(json1) .isEqualTo("{\"isbn\":\" 978-4621303252\",\"category\":{\"name\":\"java\"},\"categories\":[{\"name\":\"java\"},{\"id\":2}]}"); System.out.printf("%s: json2 = %s%n", testInfo.getDisplayName(), json2); assertThat(json2) .isEqualTo("{\"isbn\":\" 978-4621303252\"}"); }
ãããŸã§æŒãããŠããã°OKããªãšã
Spring Bootã®ããããã£ã§èšå®ãã
次ã¯ãSpring Bootã®ããããã£ã§èšå®ããŠã¿ãŸããããããã¯@JsonInclude
ã¢ãããŒã·ã§ã³ãåå¥ã«ä»äžããã®ã§ã¯ãªãã
ObjectMapper#setSerializationInclusion
ããã¬ãŒã ã¯ãŒã¯ã®æ©èœã§èšå®ããæ¹æ³ãèŠãŠããããšæããŸãã
Spring Bootãããžã§ã¯ããäœæã
$ curl -s https://start.spring.io/starter.tgz \ -d bootVersion=3.0.1 \ -d javaVersion=17 \ -d type=maven-project \ -d name=spring-boot-jackson-include-non-null \ -d groupId=org.littlewings \ -d artifactId=spring-boot-jackson-include-non-null \ -d version=0.0.1-SNAPSHOT \ -d packageName=org.littlewings.jackson.nonnull \ -d dependencies=web \ -d baseDir=spring-boot-jackson-include-non-null | tar zxvf -
ãããžã§ã¯ãå ãžç§»åã
$ cd spring-boot-jackson-include-non-null
MavenäŸåé¢ä¿ãªã©ã
<properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
ããã«ããã¹ãçšã«REST Assuredã远å ããŠãããŸããã
<dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <version>5.3.0</version> <scope>test</scope> </dependency>
èªåçæããããœãŒã¹ã³ãŒãã¯ãåé€ããŠãããŸãã
$ rm src/main/java/org/littlewings/jackson/nonnull/SpringBootJacksonIncludeNonNullApplication.java src/test/java/org/littlewings/jackson/nonnull/SpringBootJacksonIncludeNonNullApplicationTests.java
ãã®ãããžã§ã¯ãã«ãå
ã»ã©ã®Book
ã¯ã©ã¹ãšCategory
ã¯ã©ã¹ã远å ããŠãããŸãã
$ cp /path/to/src/main/java/org/littlewings/jackson/nonnull/{Book.java,Category.java} src/main/java/org/littlewings/jackson/nonnull
@JsonInclude
ããä»äžãããŠããªããæ¹ã®ã¯ã©ã¹ã§ããã
public class Book { public class Category {
main
ã¡ãœãããæã£ãã¯ã©ã¹ã«ãRestControllerãä»ããŠãããŸãã
src/main/java/org/littlewings/jackson/nonnull/App.java
package org.littlewings.jackson.nonnull; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController @SpringBootApplication public class App { public static void main(String... args) { SpringApplication.run(App.class, args); } @GetMapping("/simply") public Book simply() { return Book.create( " 978-4621303252", "Effective Java 第3ç", 4400, Category.create(1, "java"), Category.create(2, "programming") ); } @GetMapping("/with-null") public Book withNull() { return Book.create( " 978-4621303252", null, null, Category.create(null, "java"), Category.create(2, null) ); } @GetMapping("/with-null-empty-collection") public Book withNullAndEmptyCollection() { return Book.create( " 978-4621303252", null, null ); } }
ã¬ã¹ãã³ã¹ã®å 容ã¯ãJacksonåäœã§ãã¹ãããŠããæãšåããã®ã§ãã
application.properties
ã«ã¯ã以äžã®å
容ãå®çŸ©ããŸãã
src/main/resources/application.properties
spring.jackson.default-property-inclusion=non_null
ããã§ãObjectMapper#serializationInclusion
ã«JsonInclude.Include
ã®å€ãæå®ããŠããããšã«ãªããŸãã
Spring Bootã®ããããã£ãèŠãŠãããšspring.jackson.serialization.*
ãªã®ã§ã¯ïŒãšäžç¬æããããªããŸããããã¡ãã¯éããŸããã
Jackson on/off features that affect the way Java objects are serialized.
Application Properties / JSON Properties / spring.jackson.serialization.*
spring.jackson.default-property-inclusion
ã®èª¬æãã¡ãããšèŠããšãJsonInclude.Include
ã®å€ãæå®ããããããã£ã§ããããšãæžãããŠããŸãã
Application Properties / JSON Properties / spring.jackson.default-property-inclusion
Controls the inclusion of properties during serialization. Configured with one of the values in Jackson's JsonInclude.Include enumeration.
ãœãŒã¹ã³ãŒãäžã§ã確èªã
if (this.jacksonProperties.getDefaultPropertyInclusion() != null) { builder.serializationInclusion(this.jacksonProperties.getDefaultPropertyInclusion()); }
ã§ã¯ããã¹ãã³ãŒãã§ç¢ºèªããŠã¿ãŸãã
src/test/java/org/littlewings/jackson/nonnull/JacksonTest.java
package org.littlewings.jackson.nonnull; import java.net.URI; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import static io.restassured.RestAssured.given; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class JacksonTest { @LocalServerPort int port; @Test void simply() { String body = given() .get(URI.create("http://localhost:" + port + "/simply")) .asString(); assertThat(body).isEqualTo("{\"isbn\":\" 978-4621303252\",\"title\":\"Effective Java 第3ç\",\"price\":4400,\"category\":{\"id\":1,\"name\":\"java\"},\"categories\":[{\"id\":1,\"name\":\"java\"},{\"id\":2,\"name\":\"programming\"}]}"); } @Test void withNull() { String body = given() .get(URI.create("http://localhost:" + port + "/with-null")) .asString(); assertThat(body).isEqualTo("{\"isbn\":\" 978-4621303252\",\"category\":{\"name\":\"java\"},\"categories\":[{\"name\":\"java\"},{\"id\":2}]}"); } @Test void withNullAndEmptyCollection() { String body = given() .get(URI.create("http://localhost:" + port + "/with-null-empty-collection")) .asString(); assertThat(body).isEqualTo("{\"isbn\":\" 978-4621303252\",\"categories\":[]}"); } }
null
ã®é
ç®ããã¹ãŠãªããªã£ãŠããŸããOKã§ããã
åœç¶ã§ããã以äžã®ããã«èšå®ãåé€ãããš
#spring.jackson.default-property-inclusion=non_null
å€ãnull
ã®ããããã£ãå«ãŸããããã«ãªããŸãã
{"isbn":" 978-4621303252","title":null,"price":null,"category":{"id":null,"name":"java"},"categories":[{"id":null,"name":"java"},{"id":2,"name":null}]} {"isbn":" 978-4621303252","title":null,"price":null,"category":null,"categories":[]}
ãšããã§ãå
ã»ã©Jacksonåäœã®æã«è¡ã£ãŠããJsonInclude.Include
ã®NON_NULL
ãšNON_EMPTY
ãåæã«æå®ãããããªããšã¯ã
èšå®ã§ã¯ã§ããªãããã§ããã
ãã®å Žåã¯Jackson2ObjectMapperBuilderCustomizer
ã§ãªããšãããã®ããªããšæããŸãã
@Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return jackson2ObjectMapperBuilder -> { jackson2ObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_NULL); jackson2ObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_EMPTY); }; }
Quarkusã®ããããã£ã§èšå®ãã
æåŸã¯Quarkusã§ãããã¡ãã@JsonInclude
ã¢ãããŒã·ã§ã³ãåå¥ã«ä»äžããã®ã§ã¯ãªããObjectMapper#setSerializationInclusion
ã
ãã¬ãŒã ã¯ãŒã¯ã®æ©èœã§èšå®ããæ¹æ³ãèŠãŠããããšæããŸãã
Quarkusãããžã§ã¯ããäœæã
$ mvn io.quarkus.platform:quarkus-maven-plugin:2.15.3.Final:create \ -DprojectGroupId=org.littlewings \ -DprojectArtifactId=quarkus-jackson-include-non-null \ -DprojectVersion=0.0.1-SNAPSHOT \ -Dextensions='resteasy-reactive,resteasy-reactive-jackson' \ -DnoCode
RESTEasyã¯ããªããšãªãReactiveã«ããŠããŸãã
ãããžã§ã¯ãå ã«ç§»åã
$ cd quarkus-jackson-include-non-null
MavenäŸåé¢ä¿ãªã©ã
<properties> <compiler-plugin.version>3.10.1</compiler-plugin.version> <maven.compiler.release>17</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id> <quarkus.platform.version>2.15.3.Final</quarkus.platform.version> <skipITs>true</skipITs> <surefire-plugin.version>3.0.0-M7</surefire-plugin.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>${quarkus.platform.group-id}</groupId> <artifactId>${quarkus.platform.artifact-id}</artifactId> <version>${quarkus.platform.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-reactive</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-reactive-jackson</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-arc</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-junit5</artifactId> <scope>test</scope> </dependency> </dependencies>
ããã«REST Assuredã远å ããŠãããŸãã
<dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <scope>test</scope> </dependency>
ãã®ãããžã§ã¯ãã«ãå
ã»ã©ã®Book
ã¯ã©ã¹ãšCategory
ã¯ã©ã¹ã远å ããŠãããŸãã
$ cp /path/to/src/main/java/org/littlewings/jackson/nonnull/{Book.java,Category.java} src/main/java/org/littlewings/jackson/nonnull
@JsonInclude
ããä»äžãããŠããªããæ¹ã®ã¯ã©ã¹ã§ããã
public class Book { public class Category {
JAX-RSãªãœãŒã¹ã¯ã©ã¹ãäœæã
src/main/java/org/littlewings/jackson/nonnull/BookResource.java
package org.littlewings.jackson.nonnull; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import io.smallrye.mutiny.Uni; @Path("") public class BookResource { @GET @Path("simply") @Produces(MediaType.APPLICATION_JSON) public Uni<Book> simply() { return Uni .createFrom() .item( Book.create( " 978-4621303252", "Effective Java 第3ç", 4400, Category.create(1, "java"), Category.create(2, "programming") ) ); } @GET @Path("with-null") @Produces(MediaType.APPLICATION_JSON) public Uni<Book> withNull() { return Uni .createFrom() .item( Book.create( " 978-4621303252", null, null, Category.create(null, "java"), Category.create(2, null) ) ); } @GET @Path("with-null-empty-collection") @Produces(MediaType.APPLICATION_JSON) public Uni<Book> withNullAndEmptyCollection() { return Uni .createFrom() .item( Book.create( " 978-4621303252", null, null ) ); } }
RESTEasy Reactiveã䜿ã£ãŠãããšãã以å€ã¯ãSpring Bootã®æãšããå€ãããŸããã
application.properties
ã«ã¯ã以äžã®å
å®¹ãæžããŠãããŸãã
src/main/resources/application.properties
quarkus.jackson.serialization-inclusion=non-null
ãã¡ãã®ããããã£ã§ããã
All configuration options / Jackson / quarkus.jackson.serialization-inclusion
Quarkusã®å Žåãæå®ã§ããå€ãæžãããŠããã®ã§ããããããããªãšæããŸãã
always, non-null, non-absent, non-empty, non-default, custom, use-defaults
ãã¹ãã
src/test/java/org/littlewings/jackson/nonnull/BookResourceTest.java
package org.littlewings.jackson.nonnull; import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; import static io.restassured.RestAssured.given; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @QuarkusTest @TestHTTPEndpoint(BookResource.class) class BookResourceTest { @Test void simply() { String body = given() .get("/simply") .asString(); assertThat(body, is("{\"isbn\":\" 978-4621303252\",\"title\":\"Effective Java 第3ç\",\"price\":4400,\"category\":{\"id\":1,\"name\":\"java\"},\"categories\":[{\"id\":1,\"name\":\"java\"},{\"id\":2,\"name\":\"programming\"}]}")); } @Test void withNull() { String body = given() .get("/with-null") .asString(); assertThat(body, is("{\"isbn\":\" 978-4621303252\",\"category\":{\"name\":\"java\"},\"categories\":[{\"name\":\"java\"},{\"id\":2}]}")); } @Test void withNullAndEmptyCollection() { String body = given() .get("/with-null-empty-collection") .asString(); assertThat(body, is("{\"isbn\":\" 978-4621303252\",\"categories\":[]}")); } }
ãªããquarkus.jackson.serialization-inclusion
ã®æå®ãåé€ãããš
src/main/resources/application.properties
#quarkus.jackson.serialization-inclusion=non-null
ãã®ãã¹ãã¯ãã¯ã倱æããŸãã
{\"isbn\":\" 978-4621303252\",\"title\":null,\"price\":null,\"category\":{\"id\":null,\"name\":\"java\"},\"categories\":[{\"id\":null,\"name\":\"java\"},{\"id\":2,\"name\":null}]} {\"isbn\":\" 978-4621303252\",\"title\":null,\"price\":null,\"category\":null,\"categories\":[]}
Quarkusã®å ŽåããJsonInclude.Include
ã®NON_NULL
ãšNON_EMPTY
ãåæã«æå®ãããããªããšã¯ããããã£æå®ã§ã¯
ã§ããªãããã§ãã
if (serializationInclusion != null) { objectMapper.setSerializationInclusion(serializationInclusion); }
ãããå®çŸãããå Žåã¯ãObjectMapperCustomizer
ã䜿ãã°ããããã§ãã
ãããªæãã§ããã
src/main/java/org/littlewings/jackson/nonnull/ObjectMapperCustomizerImpl.java
package org.littlewings.jackson.nonnull; import javax.enterprise.context.ApplicationScoped; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.jackson.ObjectMapperCustomizer; @ApplicationScoped public class ObjectMapperCustomizerImpl implements ObjectMapperCustomizer { @Override public void customize(ObjectMapper objectMapper) { objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); } }
ãªããšãªãã@Singleton
ã§ã¯ãªã@ApplicationScoped
ã«ããŠãããŸããâŠã
Quarkusã®ãã¹ãã§ã䌌ããããªããšãããŠããŸããã
@Override public void customize(ObjectMapper objectMapper) { objectMapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false) .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) .configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false) .setSerializationInclusion(JsonInclude.Include.NON_NULL) .setSerializationInclusion(JsonInclude.Include.NON_ABSENT); }
ãããªãšããã§ããããã
ãŸãšã
Jacksonã䜿ã£ãŠãªããžã§ã¯ããã·ãªã¢ã©ã€ãºããéã«ãnull
ã®ããããã£ãåºåå
容ã«å«ããªãæ¹æ³ã確èªããŠãã£ãŠã¿ãŸããã
JacksonåäœãSpring BootãQuarkusã察象ã«ã
@JsonInclude
ã§ã®æå®ã¯ãã£ããç°¡åã«èŠã€ããã®ã§ãããSpring Bootã§ã®æå®ããããã«ããïŒèª¿ã¹ã«ããæããããã®ã§
ã¡ã¢ãå
ŒããŠãšããæãã§ã
ä»éçã«ããããããšå匷ã«ãªããŸãããã