Study Index

Data Persistence, JSON & Jackson

My working notes on getting data out of Java objects and onto the wire, then back again. Serialization, the JSON format, and the Jackson library (ObjectMapper + annotations).

serialization JSON syntax ObjectMapper @JsonProperty @JsonIgnoreProperties POJO ↔ JSON
+ = my own enrichment / correction, not from the original material.

I. Notebook Summary (copy by hand)

Telegraphic, high-density. Keyword → rule. Everything here is derivable from Section II.

═══ PERSISTENCE & SERIALIZATION ═══

Collections data    → lives in RAM → gone when program ends
Persist data        → write to file / DB / network
Cross-language       → need a NEUTRAL text format (not Java bytes)

Serialization   = Object  → portable format (string/bytes)   [encode]
Deserialization = format → Object                       [decode]
  mnemonic: SERIAL = SEND (out) ; DE = DEcode back in
two boxes: [Java Object] →(serialize)→ {"json"} →(deserialize)→ [Java Object]. one arrow each way.
═══ DATA FORMATS ═══

JSON  → most common on internet, human-readable, microservices' default
XML   → older, verbose, tag-based                       [+]
CSV   → flat tabular, no nesting                       [+]
Binary: Avro / Parquet / ORC / Protobuf → compact, fast, NOT human-readable
═══ JSON SYNTAX ═══

object   { }   unordered set of "name": value pairs
pair     "name" : value     pairs separated by ,
string   "in double quotes"   (keys ALWAYS strings in dq)
number   42  25.7  -3e10   (no quotes; int OR decimal)
boolean  true | false  (lowercase)
null     empty value for ANY type
array   [ ]   ordered list of values
nesting  objects/arrays inside values = allowed

[+] NO trailing comma. NO comments. NO single quotes. Keys must be strings.
a curly-brace box; inside, stacked rows "key : value"; one row's value is another smaller box (nesting).
═══ JACKSON CORE ═══

Lib = Jackson ; the engine = ObjectMapper (om)
POJO = Plain Old Java Object (plain fields, no framework ties) [+]

SERIALIZE     om.writeValue(file, obj)        obj → JSON file
              om.writeValueAsString(obj)      obj → JSON String   [+]
DESERIALIZE   om.readValue(json, Foo.class)   JSON → Foo object

How map happens: by FIELD NAME (key "age" → field age)
  match needs: matching name + getter/setter OR public field   [+]
center gear labeled "ObjectMapper"; left arrow in = {json}, right arrow out = [Object]. gear converts both ways.

writeValue vs readValue

Method Direction In → Out
writeValue(out, obj) serialize Object → JSON (to file/stream)
writeValueAsString(obj) + serialize Object → JSON String
readValue(src, Cls.class) deserialize JSON → Object
═══ MISMATCH / EDGE BEHAVIOR ═══

field renamed (symbol→symbl)  → deserialize: field stays NULL/default
                                 serialize: key uses NEW java name
type mismatch (int→String)    → Jackson coerces if it can; else throws
                                 (e.g. "100"→int OK; "abc"→int FAILS) [+]
duplicate keys in JSON        → LAST one wins (no error by default) [+]
extra/unknown field in JSON   → THROWS UnrecognizedPropertyException
═══ ANNOTATIONS (fix the mismatches) ═══

@JsonProperty("json_key")   on field → map field <-> that exact key
                              (decouple Java name from JSON name)

@JsonIgnoreProperties(ignoreUnknown=true)  on CLASS
                              → silently skip unknown JSON fields

@JsonIgnore   [+]  on field → never (de)serialize this one field

Annotation quick-pick

Problem Annotation Goes on
JSON key ≠ Java field name @JsonProperty field/getter
JSON has extra unknown keys @JsonIgnoreProperties(ignoreUnknown=true) class
Hide one field both ways + @JsonIgnore field
═══ MENTAL MODEL ═══

[Java world]  <--ObjectMapper-->  [JSON text world]
   objects        gears/engine        portable strings
   field names   annotations fix     json keys
                  the name gaps

II. Detailed Study Notes

One numbered concept each: claim → plain-English build-up → why it matters → traps. + marks my additions and corrections.

1. Persistence: why in-memory data disappears

Data stored in Java collections lives only in RAM, so it vanishes the moment the program stops; to keep it, you must write it somewhere durable.

When I put objects into an ArrayList, HashMap, or any collection, those objects sit in the JVM's heap, which is just a region of RAM. RAM is volatile: cut the power (or end the process) and it's wiped. So once main() returns, every collection and everything in it is gone.

Persistence means surviving beyond a single program run. To persist data I have to move it out of RAM into something non-volatile: a file on disk, a database, or send it across the network to another machine that stores it. + The technical distinction is volatile (RAM, registers, cache) vs non-volatile (disk, SSD, DB) storage.

Why it mattersAlmost every real app outlives one run, a user closes it and reopens it, a server restarts. Without persistence there's no saved game, no shopping cart, no user account. This is the entire reason serialization exists.
AnalogyRAM is a whiteboard, fast to write, but erased every night by the cleaner. Disk is a notebook, slower, but the notes are still there tomorrow.

2. Why persisted data must be language-neutral

If you want a program in any language to read your saved data, you can't save raw Java objects, you save a neutral, text-based format.

A Java object's in-memory layout is specific to the JVM. A Python or Go program has no idea what a Java HashMap's bytes mean. So when data needs to cross language (or machine) boundaries, we agree on a common, portable format, usually plain text with a documented structure. Both sides know the format's rules, so both can read and write it.

This is exactly why data on the internet travels as text formats like JSON rather than as Java/Python objects: the receiver might be written in a totally different language.

Why it mattersMicroservices, public APIs, and browser↔server traffic are all cross-language by nature. A neutral format is what lets a JavaScript frontend talk to a Java backend talking to a Python ML service.
Interview angle"Why JSON over Java serialization for an API?" → Java's built-in Serializable produces JVM-specific binary that only Java can read, and it's brittle across versions. JSON is language-agnostic and human-readable. +

3. Serialization & Deserialization

Serialization converts an in-memory object into a portable format; deserialization rebuilds the object from that format. They're encode/decode for data.

Serialization = take a live object and turn it into a flat sequence (a string or byte stream) you can store or send. Deserialization = take that flat sequence and reconstruct the equivalent object in memory. The pair is sometimes written (de)serialization.

The same idea shows up everywhere, even YouTube: video is encoded into data packets to transmit, then a decoder on your device turns packets back into a playable stream. Encoding/decoding is the general name; serialization/deserialization is that idea applied to program objects. + "Marshalling / unmarshalling" is a near-synonym you'll see in other ecosystems.

Why it mattersEvery save-to-disk, every API call, every cache write is serialization underneath. It's the bridge between "data I can compute on" and "data I can store or send."
Common misconceptionSerialization is not "compression" and not "encryption." It changes representation (object → text), not size-for-secrecy. You can compress or encrypt the serialized output afterward, but that's separate. +

→ See code: serialize/deserialize round-trip

4. Data file formats: JSON and the field

JSON is the dominant human-readable format for internet data exchange; binary formats exist for speed and size but you can't read them by eye.

JSON (JavaScript Object Notation) is the most common format for transferring data over the internet. It's plain text, so a human can open and read it, and microservices overwhelmingly use it to exchange information. Beyond JSON there's a whole family:

Format Style Human-readable? Typical use
JSON text Yes web APIs, configs, microservices
XML + text (tags) Yes (verbose) legacy enterprise, SOAP, configs
CSV + text (rows) Yes flat tabular exports, spreadsheets
Avro / Parquet / ORC binary No big-data storage, analytics
Protobuf + binary No fast service-to-service RPC (gRPC)
Trade-offsJSON pros: readable, ubiquitous, easy to debug, schema-flexible. JSON cons: larger than binary, slower to parse, no built-in types for dates/binary, no native schema enforcement. + Binary formats (Parquet, Protobuf) are smaller and faster but opaque, you need tooling to inspect them.
Good to know+ Parquet/ORC are columnar (store column-by-column) which is great for analytics scans; Avro is row-based and schema-carrying. You don't need the internals now, just that "binary = compact + fast + unreadable."

5. JSON syntax rules

JSON is built from objects, arrays, and a small fixed set of value types, with strict punctuation.

A JSON document is one value. Usually that value is an object: an unordered set of "name": value pairs wrapped in { }. The pieces:

member.jsonjson
{
  "firstName": "John",
  "lastName": "Doe",
  "rollNumber": 3925,
  "bodyMassIndex": 25.7,
  "married": false,
  "phoneNumbers": [2405820570, 750285042],
  "address": { "city": "Atlanta", "zipCode": 50005 },
  "middleName": null
}
Pitfalls (strict rules)+ JSON forbids: trailing commas ([1,2,] ✗), comments (// ... ✗), single quotes ('x' ✗), and unquoted keys ({x:1} ✗). It also can't natively represent dates, NaN/Infinity, or binary, those get encoded as strings/numbers by convention.
Good to know+ A leading zero like 0750285042 is actually invalid JSON for a number; that's why the example above drops it. Phone numbers are better stored as strings anyway (you never do math on them, and leading zeros matter).

6. POJO: the Java side of the bridge

A POJO is a plain Java class of simple fields that Jackson maps to and from JSON.

+ POJO = Plain Old Java Object, an ordinary class with fields (and usually getters/setters), not tied to any framework or special base class. Java holds information as objects, so to (de)serialize I need a class whose shape mirrors the JSON.

Member.javajava
public class Member {
    public String name;
    public Integer age;
    public String secretIdentity;
}

Given JSON {"name":"JJonahJameson","age":60,"secretIdentity":"OmniMan"}, Jackson lines up each JSON key with the same-named Java field.

Why it mattersThe POJO is the schema. Its field names and types define how the JSON is interpreted, so mismatches between POJO and JSON are the root of nearly every Jackson bug.

7. Jackson & ObjectMapper

Jackson is the Java library for JSON; its ObjectMapper is the one object that does both serialization and deserialization.

Jackson is the de-facto JSON library in Java. The workhorse is ObjectMapper. Two directions, two methods:

Goal Call Result
Serialize to file om.writeValue(outputFile, m) writes JSON to that file
Serialize to String + om.writeValueAsString(m) returns JSON as a String
Deserialize om.readValue(input, Member.class) returns a Member

+ Correction worth noting: writeValue(File, obj) returns void (it writes to the file). If you want the JSON as a return value, use writeValueAsString(obj). The original "String s = om.writeValue(...)" mixes the two, the file overload doesn't return the string.

How does Jackson know which field maps to which key? By name matching: JSON key "age" → field/property named age. + Under the hood Jackson matches against properties, which it discovers from public fields or from getter/setter method names (getAge() → property age).

Best practice+ Create one ObjectMapper and reuse it, it's thread-safe after configuration and somewhat expensive to construct. Don't make a new one per call in hot paths.

→ See code: ObjectMapper round-trip

8. Activity 1, parsing stock trades & edge cases

A real array of trade objects maps to a List<Trade>, and poking at the POJO reveals exactly how Jackson handles mismatches.

Input is an array, so deserialize into a list, not a single object. The questions this activity provokes (and their answers):

You change… What Jackson does
Field symbol → symbl JSON key "symbol" no longer matches → on deserialize the field stays null/default; on serialize the key becomes "symbl".
Field quantity type int → String Jackson coerces compatible values (number 100 → String "100" works). + Going the other way, a non-numeric string into an int throws.
Duplicate keys in JSON + By default the last occurrence wins; no error is raised (can be made strict via a parser feature).
PitfallA silently-null field is the nastiest Jackson bug, no exception, just missing data downstream. When a value is mysteriously null after deserialization, the first suspect is a name mismatch between JSON key and Java field.

→ See code: Trade list parsing + edge experiments

9. Activity 2, @JsonProperty when keys don't match

When the JSON key and your Java field name differ, @JsonProperty declares the exact key to bind to, no need to rename your fields.

Sometimes the incoming JSON uses keys you can't or don't want to mirror in Java (e.g. "1.symbol", or snake_case keys vs camelCase fields). Renaming Java fields to match ugly keys is bad, so instead annotate:

Trade.javajava
public class Trade {
    @JsonProperty("1.symbol")
    public String symbol;

    @JsonProperty("2.quantity")
    public int quantity;

    @JsonProperty("3.purchaseDate")
    public String purchaseDate;
}

Now "1.symbol" binds to symbol both ways: on deserialize the key fills the field; on serialize the field is written back out as "1.symbol". + The annotation is bidirectional, it controls both reading and writing.

Why it mattersIt decouples your clean Java naming from whatever the external API decided to call things. You keep idiomatic field names; the wire format stays correct.
Good to know+ For an entire class of snake_case ↔ camelCase, you don't annotate every field, set a naming strategy once: om.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE). Use @JsonProperty for one-off oddball keys.

→ See code: @JsonProperty mapping

10. Activity 2.1, @JsonIgnoreProperties for unknown fields

When JSON contains fields your POJO doesn't have, Jackson throws by default; @JsonIgnoreProperties(ignoreUnknown=true) tells it to skip them quietly.

If the JSON gains an extra key (say "4.weather":"Rainy") that has no matching field, default Jackson raises UnrecognizedPropertyException and deserialization fails. You rarely want a new upstream field to crash your parser, so:

Trade.javajava
@JsonIgnoreProperties(ignoreUnknown = true)
public class Trade {
    @JsonProperty("1.symbol")        public String symbol;
    @JsonProperty("2.quantity")      public int quantity;
    @JsonProperty("3.purchaseDate")  public String purchaseDate;
}

The annotation goes on the class. Now unknown keys are ignored and parsing succeeds.

Variant Effect
@JsonIgnoreProperties(ignoreUnknown=true) ignore any unknown JSON key
@JsonIgnoreProperties({"weather","note"}) + ignore those named keys specifically
@JsonIgnore on a field + never read/write that one field (e.g. a password)
Trade-offIgnoring unknowns makes you resilient to upstream additions, but also silent, if a real field gets misspelled upstream you won't notice. For strict contracts you might prefer to fail loudly. + A global alternative: om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) applies the same leniency mapper-wide.
Interview angle"How do you make a service tolerant of API changes?" → ignore unknown properties so new upstream fields don't break old clients (forward-compatibility). +

→ See code: @JsonIgnoreProperties


Connections between these ideas

III. Code Samples (runnable)

Real, runnable Java + Jackson. Each block has a filename, a run command, and expected output. + Setup uses the Jackson Databind dependency; see the Lab for the one-time setup.

A. ObjectMapper round-trip (serialize + deserialize)

Member.javajava
public class Member {
    // public fields so Jackson can see them with no getters/setters
    public String name;
    public Integer age;
    public String secretIdentity;

    // Jackson needs a no-arg constructor to build the object on read
    public Member() {}
    public Member(String name, Integer age, String id) {
        this.name = name; this.age = age; this.secretIdentity = id;
    }
}
Main.javajava
import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
    public static void main(String[] args) throws Exception {
        ObjectMapper om = new ObjectMapper();

        // --- SERIALIZE: object -> JSON string ---
        Member m = new Member("JJonahJameson", 60, "OmniMan");
        String json = om.writeValueAsString(m);   // returns the JSON text
        System.out.println(json);

        // --- DESERIALIZE: JSON string -> object ---
        String input = "{\"name\":\"Peter\",\"age\":29,\"secretIdentity\":\"Spidey\"}";
        Member back = om.readValue(input, Member.class);
        System.out.println(back.name + " / " + back.age);
    }
}
gradle run # or: javac -cp jackson-*.jar *.java && java -cp .:jackson-*.jar Main
Expected output{"name":"JJonahJameson","age":60,"secretIdentity":"OmniMan"} Peter / 29
Why it behaves this way+ The no-arg constructor matters: on readValue Jackson first creates an empty Member(), then sets each field, so without it you get InvalidDefinitionException. Serialization doesn't need it.

B. Activity 1, parse a list of trades + edge experiments

Trade.javajava
public class Trade {
    public String symbol;
    public int quantity;
    public String purchaseDate;

    @Override public String toString() {
        return symbol + " x" + quantity + " @" + purchaseDate;
    }
}
ParseTrades.javajava
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;

public class ParseTrades {
    public static void main(String[] a) throws Exception {
        String jsonArray = "[{\"symbol\":\"AAPL\",\"quantity\":100,\"purchaseDate\":\"2019-01-02\"}," + " {\"symbol\":\"MSFT\",\"quantity\":10,\"purchaseDate\":\"2019-01-02\"}]";

        ObjectMapper om = new ObjectMapper();
        // TypeReference preserves the generic type List<Trade> at runtime
        List<Trade> trades = om.readValue(jsonArray, new TypeReference<List<Trade>>() {});

        trades.forEach(System.out::println);
    }
}
gradle run
Expected outputAAPL x100 @2019-01-02 MSFT x10 @2019-01-02
Why TypeReference?+ Java erases generics at runtime, so List.class alone loses the <Trade> part and you'd get a List<LinkedHashMap>. TypeReference captures the full type so Jackson builds real Trade objects. (Alternative: om.readValue(json, Trade[].class) for an array.)
Edge experiment outputs (predict, then expand) results
Experiment Result
Rename field symbol→symbl, keep JSON key "symbol" symbl stays null; no error. toString() prints null x100 ...
Change quantity to String Number 100 coerced to "100", works fine.
JSON value "quantity":"abc" into int Throws InvalidFormatException (can't make int from "abc"). +
Duplicate key "quantity":100,"quantity":7 Last wins → quantity == 7; no error by default. +

C. Activity 2, @JsonProperty for mismatched keys

Trade.javajava
import com.fasterxml.jackson.annotation.JsonProperty;

public class Trade {
    @JsonProperty("1.symbol")        public String symbol;
    @JsonProperty("2.quantity")      public int quantity;
    @JsonProperty("3.purchaseDate")  public String purchaseDate;

    @Override public String toString() {
        return symbol + " x" + quantity;
    }
}
Main.javajava
String json = "{\"1.symbol\":\"AAPL\",\"2.quantity\":100,\"3.purchaseDate\":\"2019-01-02\"}";
ObjectMapper om = new ObjectMapper();

Trade t = om.readValue(json, Trade.class);   // "1.symbol" -> symbol
System.out.println(t);

String out = om.writeValueAsString(t);          // writes "1.symbol" back
System.out.println(out);
gradle run
Expected outputAAPL x100 {"1.symbol":"AAPL","2.quantity":100,"3.purchaseDate":"2019-01-02"}
Note+ Same annotation governs both directions, so the round-trip key names are preserved exactly.

D. Activity 2.1, @JsonIgnoreProperties for unknown fields

Trade.javajava
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)  // skip keys we don't model
public class Trade {
    @JsonProperty("1.symbol")        public String symbol;
    @JsonProperty("2.quantity")      public int quantity;
    @JsonProperty("3.purchaseDate")  public String purchaseDate;
}
Main.javajava
// note the extra "4.weather" key that Trade has no field for
String json = "{\"1.symbol\":\"AAPL\",\"2.quantity\":100," + "\"3.purchaseDate\":\"2019-01-02\",\"4.weather\":\"Rainy\"}";

ObjectMapper om = new ObjectMapper();
Trade t = om.readValue(json, Trade.class);  // weather ignored, no crash
System.out.println(t.symbol + " " + t.quantity);
gradle run
Expected outputAAPL 100
Without the annotation+ Remove @JsonIgnoreProperties and the same input throws: UnrecognizedPropertyException: Unrecognized field "4.weather" — Jackson refuses unknown keys by default.

IV. Hands-On Lab (practice)

Six graded exercises. Predict the output before you run, then verify. Solutions are collapsed.

One-time Setup

Goal: a folder that compiles Java with Jackson on the classpath.

build.gradlegroovy
plugins { id 'application' }
repositories { mavenCentral() }
dependencies {
    // brings databind + core + annotations transitively
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
}
application { mainClass = 'Main' }
mkdir json-lab && cd json-lab # project folder
java -version # confirm JDK 17+ installed
gradle run # compile + run mainClass
No Gradle?+ Download the three Jackson jars (databind, core, annotations) and compile manually: javac -cp "libs/*" *.java && java -cp ".:libs/*" Main (use ; instead of : on Windows).

1. Serialize a single object WARM-UP

Goal: turn one Member into a JSON string.

  1. Create Member with fields name, age, secretIdentity + no-arg constructor.
  2. In Main, build a Member and call writeValueAsString.
  3. Print the result.
Predict: what's the exact key order and punctuation? Will there be spaces after colons?
Solution java
Main.javajava
ObjectMapper om = new ObjectMapper();
Member m = new Member("Ada", 36, "Countess");
System.out.println(om.writeValueAsString(m));
Expected output{"name":"Ada","age":36,"secretIdentity":"Countess"}

+ No spaces by default (compact). Want pretty output? om.writerWithDefaultPrettyPrinter().writeValueAsString(m).

2. Deserialize back into an object WARM-UP

Goal: rebuild a Member from a JSON string and read a field.

  1. Take the JSON from Ex 1 as a string literal (escape the quotes).
  2. Call readValue(json, Member.class).
  3. Print member.name and member.age.
Predict: what happens if you forget the no-arg constructor on Member?
Solution java
Main.javajava
String json = "{\"name\":\"Ada\",\"age\":36,\"secretIdentity\":\"Countess\"}";
Member m = new ObjectMapper().readValue(json, Member.class);
System.out.println(m.name + " / " + m.age);
Expected outputAda / 36

+ No no-arg constructor → InvalidDefinitionException: Cannot construct instance ... no Creators.

3. Parse a JSON array into a List CORE

Goal: deserialize the two-trade array into List<Trade> and sum the quantities.

  1. Define Trade (symbol, quantity, purchaseDate).
  2. Read with new TypeReference<List<Trade>>(){}.
  3. Loop and add up quantity.
Predict: if you used List.class instead of TypeReference, what type are the list elements?
Solution java
Main.javajava
String arr = "[{\"symbol\":\"AAPL\",\"quantity\":100,\"purchaseDate\":\"2019-01-02\"}," + " {\"symbol\":\"MSFT\",\"quantity\":10,\"purchaseDate\":\"2019-01-02\"}]";
List<Trade> ts = new ObjectMapper()
    .readValue(arr, new TypeReference<List<Trade>>(){});
int total = ts.stream().mapToInt(t -> t.quantity).sum();
System.out.println("total = " + total);
Expected outputtotal = 110

+ With List.class the elements are LinkedHashMap, not Trade — accessing t.quantity wouldn't even compile.

4. Fix a name mismatch with @JsonProperty CORE

Goal: bind JSON keys "1.symbol", "2.quantity", "3.purchaseDate" to clean fields.

  1. Start from a Trade whose fields are symbol/quantity/purchaseDate.
  2. Run against the dotted-key JSON, observe nulls/zeros.
  3. Add @JsonProperty("…") to each field, re-run.
Predict: before adding annotations, what is quantity (an int) when its key doesn't match?
Solution java
Trade.javajava
public class Trade {
    @JsonProperty("1.symbol")       public String symbol;
    @JsonProperty("2.quantity")     public int quantity;
    @JsonProperty("3.purchaseDate") public String purchaseDate;
}
Expected output (after fix)AAPL 100

+ Before the fix: symbol == null, quantity == 0 (int default). A primitive can't be null, so the tell is 0, not null.

5. Survive an unknown field CORE

Goal: deserialize JSON that has an extra "4.weather" key without crashing.

  1. Use the annotated Trade from Ex 4.
  2. Feed it JSON containing "4.weather":"Rainy", observe the exception.
  3. Add @JsonIgnoreProperties(ignoreUnknown=true) to the class, re-run.
Predict: which exact exception fires before you add the annotation?
Solution java
Trade.javajava
@JsonIgnoreProperties(ignoreUnknown = true)
public class Trade { /* annotated fields as in Ex 4 */ }
Expected output (after fix)AAPL 100

+ Before: UnrecognizedPropertyException: Unrecognized field "4.weather". Mapper-wide alternative: om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).

6. Hide a field + handle a duplicate key CHALLENGE

Goal: never serialize a sensitive field, and observe duplicate-key behavior.

  1. Add a password field to a class and mark it @JsonIgnore; serialize and confirm it's absent.
  2. Deserialize JSON with a repeated key (e.g. "quantity":100,"quantity":7) and print the value.
  3. Explain which one won and why no exception fired.
Predict: first vs last duplicate, which value survives by default? Does @JsonIgnore block both reading and writing?
Solution java
User.javajava
import com.fasterxml.jackson.annotation.JsonIgnore;

public class User {
    public String username;
    @JsonIgnore public String password;  // skipped both ways
}
Main.javajava
ObjectMapper om = new ObjectMapper();

User u = new User(); u.username = "ada"; u.password = "secret";
System.out.println(om.writeValueAsString(u));   // password omitted

String dup = "{\"quantity\":100,\"quantity\":7}";
Trade t = om.readValue(dup, Trade.class);  // Trade w/o @JsonProperty
System.out.println("quantity = " + t.quantity);
Expected output{"username":"ada"} quantity = 7

+ Last duplicate wins because Jackson processes keys in stream order, overwriting earlier values; standard JSON parsers don't error on duplicates by default. @JsonIgnore removes the property from both serialization and deserialization. To reject duplicates, enable JsonParser.Feature.STRICT_DUPLICATE_DETECTION.


Self-check rubric, how to know you've got it

★ Key Takeaways