Jackson 3 series companion code
37 runnable examples covering the eight feature posts on ankurm.com, verified against Jackson 3.2.1 on Temurin 21.0.5. Every output committed under docs/ was produced by run-all.sh. Also documents 11 places where the published snippets do not compile or do not behave as printed against a real Jackson 3 build - most notably that writeValueAsString(List<Base>) silently drops the polymorphic type discriminator, so the post's serialised output cannot be read back.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
package com.ankurm.jackson3.part1objectmapper;
|
||||
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Post: Jackson ObjectMapper Guide — https://ankurm.com/jackson-objectmapper-guide/
|
||||
* Section: "Serialising Java Objects to JSON"
|
||||
*
|
||||
* Every write target: String, File, and pretty-printed String.
|
||||
*/
|
||||
public class B01WriteJson {
|
||||
|
||||
public record Article(Long articleId, String title, List<String> tags) { }
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
JsonMapper mapper = JsonMapper.builder().build();
|
||||
Article article = new Article(1L, "Jackson Deep Dive", List.of("java", "json"));
|
||||
|
||||
// 1. Write to a String
|
||||
String jsonOutput = mapper.writeValueAsString(article);
|
||||
System.out.println(jsonOutput);
|
||||
|
||||
// 2. Write to a File
|
||||
File target = File.createTempFile("article", ".json");
|
||||
mapper.writeValue(target, article);
|
||||
System.out.println("file : " + Files.readString(target.toPath()));
|
||||
|
||||
// 3. Pretty-printed output
|
||||
String pretty = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(article);
|
||||
System.out.println("pretty :");
|
||||
System.out.println(pretty);
|
||||
|
||||
target.deleteOnExit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.ankurm.jackson3.part1objectmapper;
|
||||
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Post: Jackson ObjectMapper Guide — https://ankurm.com/jackson-objectmapper-guide/
|
||||
* Section: "Deserialising JSON to Java Objects"
|
||||
*
|
||||
* Reading from a String, a File and an InputStream.
|
||||
*
|
||||
* The post also shows readValue(new URL(...), ...). That overload is deliberately
|
||||
* NOT reproduced here: it performs a live network call, which would make this
|
||||
* example non-reproducible. The InputStream form below is what a real HTTP client
|
||||
* hands you anyway.
|
||||
*/
|
||||
public class B02ReadJson {
|
||||
|
||||
public record Article(Long articleId, String title, List<String> tags) { }
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
JsonMapper mapper = JsonMapper.builder().build();
|
||||
|
||||
String json = "{\"articleId\":1,\"title\":\"Jackson Deep Dive\",\"tags\":[\"java\",\"json\"]}";
|
||||
|
||||
// 1. Read from a String
|
||||
Article fromString = mapper.readValue(json, Article.class);
|
||||
System.out.println("from String : " + fromString.title());
|
||||
|
||||
// 2. Read from a File
|
||||
File file = File.createTempFile("article", ".json");
|
||||
Files.writeString(file.toPath(), json);
|
||||
Article fromFile = mapper.readValue(file, Article.class);
|
||||
System.out.println("from File : " + fromFile.articleId());
|
||||
|
||||
// 3. Read from an InputStream
|
||||
try (var in = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))) {
|
||||
Article fromStream = mapper.readValue(in, Article.class);
|
||||
System.out.println("from Stream : " + fromStream.tags());
|
||||
}
|
||||
|
||||
file.deleteOnExit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.ankurm.jackson3.part1objectmapper;
|
||||
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Post: Jackson ObjectMapper Guide — https://ankurm.com/jackson-objectmapper-guide/
|
||||
* Section: "Working with Collections and Generic Types"
|
||||
*
|
||||
* Why TypeReference is required, and what actually happens without it.
|
||||
*
|
||||
* Note the import: TypeReference lives in tools.jackson.core.type in Jackson 3.
|
||||
*/
|
||||
public class B03GenericCollections {
|
||||
|
||||
public record Article(Long articleId, String title) { }
|
||||
|
||||
public static void main(String[] args) {
|
||||
JsonMapper mapper = JsonMapper.builder().build();
|
||||
|
||||
String jsonArray = "[{\"articleId\":1,\"title\":\"First\"},"
|
||||
+ "{\"articleId\":2,\"title\":\"Second\"}]";
|
||||
|
||||
// CORRECT: TypeReference captures List<Article> through an anonymous subclass,
|
||||
// so the parameterised type survives erasure and reaches Jackson at runtime.
|
||||
List<Article> articles = mapper.readValue(jsonArray, new TypeReference<List<Article>>() { });
|
||||
System.out.println("size : " + articles.size());
|
||||
System.out.println("element class : " + articles.get(0).getClass().getSimpleName());
|
||||
System.out.println("first title : " + articles.get(0).title());
|
||||
|
||||
// WRONG: List.class erases the element type. This COMPILES and does not throw
|
||||
// here — the failure is deferred to the first time you treat an element as an
|
||||
// Article, which is what makes it such an unpleasant bug.
|
||||
@SuppressWarnings("rawtypes")
|
||||
List raw = mapper.readValue(jsonArray, List.class);
|
||||
System.out.println("raw element : " + raw.get(0).getClass().getSimpleName()
|
||||
+ " <- not Article");
|
||||
try {
|
||||
Article boom = (Article) raw.get(0);
|
||||
System.out.println("unreachable: " + boom);
|
||||
} catch (ClassCastException e) {
|
||||
System.out.println("cast fails : ClassCastException, as expected");
|
||||
}
|
||||
|
||||
// A Map value type needs the same treatment.
|
||||
String jsonObject = "{\"a\":{\"articleId\":9,\"title\":\"Nine\"}}";
|
||||
Map<String, Article> byKey =
|
||||
mapper.readValue(jsonObject, new TypeReference<Map<String, Article>>() { });
|
||||
System.out.println("map value : " + byKey.get("a").title());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.ankurm.jackson3.part1objectmapper;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
/**
|
||||
* BEYOND THE POST — no blog section covers this, but it bites on first run.
|
||||
*
|
||||
* A record serialises in declaration order. A getter-based POJO serialises in
|
||||
* ALPHABETICAL order. If you are diffing Jackson output against a fixture, this
|
||||
* is usually the reason the diff is not empty.
|
||||
*/
|
||||
public class B04PropertyOrdering {
|
||||
|
||||
/** Getter-based POJO: output is alphabetical, NOT declaration order. */
|
||||
public static class ProductPojo {
|
||||
private Long productId;
|
||||
private String productName;
|
||||
private double listPrice;
|
||||
public ProductPojo(Long i, String n, double p) { productId = i; productName = n; listPrice = p; }
|
||||
public Long getProductId() { return productId; }
|
||||
public String getProductName() { return productName; }
|
||||
public double getListPrice() { return listPrice; }
|
||||
}
|
||||
|
||||
/** Record: output follows the component declaration order. */
|
||||
public record ProductRecord(Long productId, String productName, double listPrice) { }
|
||||
|
||||
/** Explicit ordering wins over both defaults. */
|
||||
@JsonPropertyOrder({ "productId", "productName", "listPrice" })
|
||||
public static class ProductOrdered {
|
||||
private final Long productId;
|
||||
private final String productName;
|
||||
private final double listPrice;
|
||||
public ProductOrdered(Long i, String n, double p) { productId = i; productName = n; listPrice = p; }
|
||||
public Long getProductId() { return productId; }
|
||||
public String getProductName() { return productName; }
|
||||
public double getListPrice() { return listPrice; }
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
JsonMapper mapper = JsonMapper.builder().build();
|
||||
System.out.println("POJO : " + mapper.writeValueAsString(
|
||||
new ProductPojo(1L, "Mechanical Keyboard", 79.99)));
|
||||
System.out.println("record : " + mapper.writeValueAsString(
|
||||
new ProductRecord(1L, "Mechanical Keyboard", 79.99)));
|
||||
System.out.println("ordered : " + mapper.writeValueAsString(
|
||||
new ProductOrdered(1L, "Mechanical Keyboard", 79.99)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user