Records, Classes & Beans
@JSON can be placed on three kinds of types. All three generate the same companion API (toJSON / fromJSON); they differ only in how the processor reads values out for serialization and constructs an instance on deserialization.
Records
A record is the simplest case: its components are the members, read through the record accessors and written through the canonical constructor. No extra annotations are required.
@JSON
public record User(String name, int age, String email) {}
Classes with @JSONConstructor
For an immutable (non-record) class, mark the constructor the deserializer should call with @JSONConstructor. The constructor’s parameter names become the members.
@JSON
public class Point {
private final int x;
private final int y;
@JSONConstructor
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
}
{"x":1,"y":2} deserializes by calling new Point(1, 2) and serializes by reading getX() / getY().
Reading values back out. For a parameter named foo, the processor resolves a reader in this order: getFoo(), then isFoo() (boolean only), then foo(), then a public field foo. If a serialized parameter has no usable reader, compilation fails with a message telling you to add one (or mark the parameter write-only).
Rules:
- Exactly one
@JSONConstructor, and it must bepublic(the companion lives in a siblinginternalpackage and has to reach it). @JSONConstructoris not valid on a record.- A parameter marked
@JSONField(writeOnly = true)needs no reader — useful for inputs like a password that should never be echoed back out.
JavaBean classes
A @JSON class without a @JSONConstructor is treated as a JavaBean: the processor constructs it with the public no-arg constructor and discovers properties from accessors.
@JSON
public class Account {
private String id;
private int balance;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public int getBalance() { return balance; }
public void setBalance(int balance) { this.balance = balance; }
public int getFeeBps() { return balance > 100 ? 0 : 25; } // computed, read-only
}
Property discovery. Properties come from getFoo() / isFoo() (read), setFoo(value) (write), and public fields. A property that has only a reader is read-only (serialized but not deserialized); one with only a writer is write-only. So getFeeBps() above is serialized but never read back — the normal pattern for a computed value.
Rules:
- A public no-arg constructor is required.
- At least one property must be discovered.
staticmembers are ignored, andtransientfields are skipped.- When both a field and an accessor for the same property carry
@JSONField, the field’s annotation wins.
Polymorphic subtypes
Records, @JSONConstructor classes, and JavaBeans can all be the permitted subtypes of a sealed @JSON hierarchy — see Polymorphism.