Infographic by Guillaume Laforge
LLM applications waste 40-50% of their token budget on JSON syntax overhead — braces, brackets, quotes, and commas that carry zero semantic information. json-io is a Java serialization library that reads and writes JSON, JSON5, and TOON (Token-Oriented Object Notation) — a format that strips that overhead while preserving full data fidelity. It also handles what Jackson/Gson cannot: cyclic object graphs, automatic polymorphic types, and zero-config serialization of 60+ built-in Java types.
Featured on json.org and Baeldung. See the interactive JSON vs TOON comparison.
// JSON
String json = JsonIo.toJson(myObject);
MyClass obj = JsonIo.toJava(json).asClass(MyClass.class);
// JSON5
String json5 = JsonIo.toJson(myObject, new WriteOptionsBuilder().json5().build());
// TOON (~40-50% fewer tokens than JSON)
String toon = JsonIo.toToon(myObject);
MyClass obj = JsonIo.fromToon(toon).asClass(MyClass.class);
// Standard JSON (Jackson-compatible output — no @type, no @id/@ref, stringified map keys)
String json = JsonIo.toJson(myObject, new WriteOptionsBuilder().standardJson().build());Gradle
implementation 'com.cedarsoftware:json-io:4.101.0'Maven
<dependency>
<groupId>com.cedarsoftware</groupId>
<artifactId>json-io</artifactId>
<version>4.101.0</version>
</dependency>JSON
// Write JSON
String json = JsonIo.toJson(myObject);
JsonIo.toJson(outputStream, myObject, writeOptions);
// Read JSON to typed Java objects
Employee emp = JsonIo.toJava(json).asClass(Employee.class);
Employee emp = JsonIo.toJava(inputStream, readOptions).asClass(Employee.class);
// Read JSON to Maps (no classes needed)
Map map = JsonIo.toMaps(json).asMap();
// Generic types
List<Employee> list = JsonIo.toJava(json).asType(new TypeHolder<List<Employee>>(){});One flag flips json-io's output to match Jackson. Use standardJson() to produce output
byte-compatible with what Jackson (with JavaTimeModule and the Spring Boot default of
WRITE_DATES_AS_TIMESTAMPS=false) produces:
WriteOptions opts = new WriteOptionsBuilder()
.standardJson() // will be the default in 5.0.0 — this call becomes optional
.build();
String json = JsonIo.toJson(myObject, opts);This configures: showTypeInfoNever, showRootTypeInfo(false), cycleSupport(false),
stringifyMapKeys(true), writeOptionalAsObject(false), preserveLeafContainerIdentity(false),
isoDateFormat() (ISO-8601 dates for java.util.Date — java.time.* are already ISO-8601),
and useMetaPrefixDollar().
Use json-io anywhere you'd use Jackson for standard JSON output, and get the full json-io
feature set whenever you need it: cyclic graph preservation, polymorphic @type auto-detection,
and first-class JSON5 + TOON output — all from the same builder.
Need Jackson-style output plus graph fidelity? If your data has shared references or cycles
and you want them preserved (something Jackson only does with @JsonIdentityInfo annotated on
every affected class), turn cycle support back on after standardJson():
WriteOptions opts = new WriteOptionsBuilder()
.standardJson()
.cycleSupport(true) // Jackson-compatible output, but $id/$ref are emitted when shared refs exist
.build();You get Jackson-compatible field names, ISO-8601 dates, and stringified Map keys — plus json-io's automatic shared-reference and cycle preservation. No class annotations required.
Object graph handling
| Capability | json-io | Jackson | Gson |
|---|---|---|---|
| Cyclic object graphs | Automatic ($id/$ref) -or- JsonIoException with cycleSupport(false) |
Requires @JsonIdentityInfo |
StackOverflowError |
| Shared object references | Preserved ($id/$ref) -or- duplicated with cycleSupport(false) |
Requires @JsonIdentityInfo |
Duplicated (no identity) |
| Polymorphic types | Automatic -or- use @IoTypeInfo/@JsonTypeInfo |
Requires @JsonTypeInfo |
Requires TypeAdapter |
Unknown $type values |
Graceful fallback to Map |
Exception | Exception |
Map handling
| Capability | json-io | Jackson | Gson |
|---|---|---|---|
Map<String, V> |
Standard JSON object | Same | Same |
Map<Long, V>, Map<UUID, V> |
stringifyMapKeys → {"100": v} |
toString() (fragile) |
enableComplexMapKeySerialization() |
Map<POJO, V> (complex keys) |
Full fidelity via $keys/$items |
toString() (lossy) |
Array of key-value pairs |
Format & configuration
| Capability | json-io | Jackson | Gson |
|---|---|---|---|
| Standard JSON output | .standardJson() — Jackson-compatible |
Default | Default |
| JSON5 support | Full read/write (native) | Partial read only | None |
| TOON support | Full read/write (40-50% fewer tokens) | None | None |
| Configuration | Zero-config; optional @Io* |
Annotation-heavy (@Json*) |
Annotations + builders |
| Jackson annotations | Recognized reflectively | Native | Not supported |
| Two parse modes | toJava() (typed) + toMaps() |
Typed only | Typed only |
Runtime
| Capability | json-io | Jackson | Gson |
|---|---|---|---|
| Performance (simple DTOs) | 1.3–2.0x vs Jackson (all paths under 2x) | Fastest | ~1.3x vs Jackson |
| Dependencies | java-util only (~1MB) | Multiple JARs (~2.5MB+) | Single JAR (~300KB) |
| Java version | JDK 8+ | JDK 8+ | JDK 8+ |
On performance: Jackson is faster for simple DTOs, but json-io stays under 2x Jackson on every read/write mode on the JsonPerformanceTest benchmark (100,000 iterations, diverse POJO workload — nested collections, floats, BigDecimal, java.time.*, UUIDs, nullable fields). In real-world applications, serialization is typically <1% of total request time — the rest is network I/O, database queries, and business logic. json-io's additional capabilities (cycles, polymorphism, zero-config, JSON5, TOON) often matter more than raw serialization throughput.
Measured ratios vs Jackson (lower is faster; 1.0 = Jackson parity)
| Mode | JsonIo | TOON |
|---|---|---|
Read toJava (typed) |
1.92x | 1.97x |
Read toMaps (class-independent) |
1.32x | 1.73x |
Write cycleSupport=true (default) |
1.75x | 1.78x |
Write cycleSupport=false (DTOs/acyclic) |
1.60x | 1.66x |
Write toMaps cycleSupport=true |
1.83x | 1.84x |
Write toMaps cycleSupport=false |
1.57x | 1.68x |
Measured on JDK 21, json-io 4.101.0 vs jackson-databind 2.21.2. Reproduce with mvn -q -pl json-io -DskipTests test-compile exec:java -Dexec.classpathScope=test -Dexec.mainClass=com.cedarsoftware.io.JsonPerformanceTest (100k iterations after 10k warmup; expect ±3% run-to-run noise from thermal / GC). Jackson is configured with JavaTimeModule and WRITE_DATES_AS_TIMESTAMPS=false to match what Spring Boot emits by default.
Performance tip: Use cycleSupport(false) for ~35-40% faster writes when your data is acyclic (DTOs, POJOs, tree-shaped data).
- Jackson-compatible standard JSON —
writeOptions.standardJson()matches Spring Boot's default Jackson output (ISO-8601 dates, stringified Map keys, no proprietary metadata). Drop-in for Jackson. - Two modes: typed Java objects (
toJava()) or class-independent Maps (toMaps()) - Preserves object references and handles cyclic relationships via
$id/$ref— zero annotations required (Jackson needs@JsonIdentityInfoon every class) - Supports polymorphic types and complex object graphs
- Zero external dependencies (other than java-util)
- Stringify-able map keys:
Map<Long, V>writes{"100": value}withstringifyMapKeys(true) - Extensive configuration via
ReadOptionsBuilderandWriteOptionsBuilder - Parse JSON with unknown
$typereferences into a Map-of-Maps without requiring classes on classpath
JSON5
JSON5 is an extension to JSON that makes it more human-friendly. json-io provides complete JSON5 support for both reading and writing — the only major Java JSON library to do so natively.
json-io accepts all JSON5 extensions by default — no configuration needed:
String json5 = "{ name: 'John', age: 30, /* comment */ }";
Person p = JsonIo.toJava(json5).asClass(Person.class);Supported: single-line (//) and multi-line (/* */) comments, single-quoted strings, unquoted keys, trailing commas, hex integers, Infinity/NaN/-Infinity, and more.
WriteOptions opts = new WriteOptionsBuilder().json5().build();
String json5 = JsonIo.toJson(myObject, opts);The json5() umbrella enables:
- Unquoted keys — object keys that are valid identifiers are written without quotes
- Smart quotes — strings containing
"(but not') use single quotes - Infinity/NaN literals — special float/double values as literals instead of
null - Stringify map keys —
Map<Long, V>writes{100: value}instead of$keys/$items - No type info —
showTypeInfoNever()+cycleSupport(false)for clean output
Individual features can be enabled separately. See the User Guide for details.
TOON
TOON (Token-Oriented Object Notation) is an indentation-based format that produces ~40-50% fewer tokens than JSON — ideal for LLM applications where token count directly impacts cost and context window usage.
- No braces, brackets, or commas — structure is expressed through indentation
- No quoting for most keys and values — quotes only when needed
- Compact arrays — inline
[N]: a,b,cor list format with-prefixed elements - Tabular format — arrays of uniform objects as CSV-like rows with column headers
- Key folding — nested keys like
address.city: Denverflatten one level of nesting - Full fidelity — most data requires no extra metadata at all
JSON:
{"team":"Rockets","players":[{"name":"John","age":30,"position":"guard"},{"name":"Sue","age":27,"position":"forward"},{"name":"Mike","age":32,"position":"center"}]}TOON (same data, ~45% fewer tokens):
team: Rockets
players:
name, age, position
John, 30, guard
Sue, 27, forward
Mike, 32, center// Write TOON
String toon = JsonIo.toToon(myObject);
// Read TOON back to typed Java object
Person p = JsonIo.fromToon(toon).asClass(Person.class);
// Read TOON to Maps (no class needed)
Map map = JsonIo.fromToon(toon).asMap();Request TOON format for LLM applications: Accept: application/vnd.toon
See the Baeldung tutorial for a walkthrough of JSON, JSON5, and TOON serialization with json-io, and the interactive JSON vs TOON comparison.
| Capability | json-io | JToon |
|---|---|---|
| Built-in types | 60+ | ~15 |
| Map key types | Any serializable type | Strings only |
| EnumSet support | Yes | No |
| Full Java serialization | Yes — any object graph | Limited to supported types |
Cycle support ($id/$ref) |
Yes (opt-in) | No |
| Annotation support | @Io* + Jackson (reflective) |
None |
| Dependencies | java-util only | Jackson |
| Status | Stable, production-ready | Beta (v1.x.x) |
Spring Boot Integration
json-io provides a Spring Boot starter for seamless integration with Spring MVC and WebFlux applications.
<dependency>
<groupId>com.cedarsoftware</groupId>
<artifactId>json-io-spring-boot-starter</artifactId>
<version>4.101.0</version>
</dependency>Your REST controllers now support JSON, JSON5, and TOON formats via content negotiation:
@RestController
public class ApiController {
@GetMapping("/data")
public MyData getData() {
return myData; // Returns JSON, JSON5, or TOON based on Accept header
}
}json-io provides a Spring AI module that reduces LLM token usage by ~40-50% using TOON format for tool call results and structured output parsing.
<dependency>
<groupId>com.cedarsoftware</groupId>
<artifactId>json-io-spring-ai-toon</artifactId>
<version>4.101.0</version>
</dependency>Auto-configured: tool call results are serialized to TOON automatically. For structured output, use ToonBeanOutputConverter<T>:
ToonBeanOutputConverter<Person> converter = new ToonBeanOutputConverter<>(Person.class);
Person person = chatClient.prompt()
.user("Get info about John")
.call()
.entity(converter);Also supports WebFlux and WebClient for reactive applications.
See the Spring Integration Guide for configuration options, WebFlux usage, customizers, and Jackson coexistence modes.
Annotations, Types & Configuration
json-io provides 25 annotations in the com.cedarsoftware.io.annotation package for controlling serialization and deserialization:
| Annotation | Target | Purpose |
|---|---|---|
@IoProperty("name") |
Field | Rename field in JSON |
@IoIgnore |
Field | Exclude field |
@IoIgnoreProperties({"a","b"}) |
Class | Exclude fields by name |
@IoAlias({"alt1","alt2"}) |
Field | Accept alternate names on read |
@IoPropertyOrder({"x","y"}) |
Class | Control field order on write |
@IoInclude(Include.NON_NULL) |
Field | Skip null on write |
@IoCreator |
Constructor/Method | Custom deserialization constructor or static factory |
@IoValue |
Method | Single-value serialization |
@IoNaming(Strategy.SNAKE_CASE) |
Class | Naming strategy for all fields |
@IoIncludeProperties({"a","b"}) |
Class | Whitelist of included fields |
@IoIgnoreType |
Class | Exclude all fields of this type everywhere |
@IoTypeInfo(LinkedList.class) |
Field | Default concrete type when $type absent; also eliminates $type on write when runtime type matches |
@IoDeserialize(as=LinkedList.class) |
Field/Class | Force type override during deserialization; also eliminates $type on write when runtime type matches |
@IoClassFactory(MyFactory.class) |
Class | Specify a ClassFactory for deserialization |
@IoGetter("fieldName") |
Method | Custom getter method for serialization |
@IoSetter("fieldName") |
Method | Custom setter method for deserialization |
@IoNonReferenceable |
Class | Suppress $id/$ref for instances of this type |
@IoNotCustomReader |
Class | Suppress custom reader (use standard deserialization) |
@IoNotCustomWritten |
Class | Suppress custom writer (use standard serialization) |
@IoCustomWriter(MyWriter.class) |
Class | Specify custom JsonClassWriter for serialization |
@IoCustomReader(MyReader.class) |
Class | Specify custom JsonClassReader for deserialization |
@IoTypeName("ShortName") |
Class | Alias for $type in JSON (replaces FQCN) |
@IoAnySetter |
Method | Receive unrecognized JSON fields during deserialization |
@IoAnyGetter |
Method | Provide extra fields during serialization |
@IoFormat("pattern") |
Field | Per-field format pattern (String.format, DecimalFormat, DateTimeFormatter, or SimpleDateFormat) |
Additionally, json-io reflectively honors Jackson annotations when they are on the classpath — with zero compile-time dependency on Jackson. Supported: @JsonProperty, @JsonIgnore, @JsonIgnoreProperties, @JsonAlias, @JsonPropertyOrder, @JsonInclude, @JsonCreator, @JsonValue, @JsonIgnoreType, @JsonTypeInfo, @JsonIncludeProperties, @JsonNaming, @JsonDeserialize, @JsonGetter, @JsonSetter, @JsonTypeName, @JsonFormat, @JsonAnySetter, @JsonAnyGetter.
Precedence: Programmatic API > json-io annotations > Jackson annotations.
See the Annotations section of the User Guide for full details and examples.
json-io handles your business objects, DTOs, and Records automatically—no annotations required. It also provides optimized handling for these built-in types:
| Category | Types |
|---|---|
| Primitives | byte, short, int, long, float, double, boolean, char + wrappers |
| Numbers | BigInteger, BigDecimal, AtomicInteger, AtomicLong, AtomicBoolean |
| Date/Time | Date, Calendar, Instant, LocalDate, LocalTime, LocalDateTime, ZonedDateTime, OffsetDateTime, OffsetTime, Duration, Period, Year, YearMonth, MonthDay, TimeZone, ZoneId, ZoneOffset, java.sql.Date, Timestamp |
| Strings | String, StringBuffer, StringBuilder, char[], CharBuffer |
| Binary | byte[], ByteBuffer, BitSet |
| IDs | UUID, URI, URL, Class, Locale, Currency, Pattern, File, Path |
| Geometric | Color, Dimension, Point, Rectangle, Insets |
| Other | Enum (any), Throwable, all Collection, Map, EnumSet, and array types |
See the complete type comparison showing json-io's comprehensive support vs other TOON implementations.
- Fully compatible with both JPMS and OSGi environments
- Zero external dependencies (other than java-util)
- Lightweight (
json-io.jaris ~330K,java-utilis ~700K) - Compatible with JDK 1.8 through JDK 24
- The library is built with the
-parameterscompiler flag. Parameter names are now retained for tasks such as constructor discovery. - Optional unsafe mode for deserializing package-private classes, inner classes, and classes without accessible constructors (opt-in for security)
- Extensive configuration options via
ReadOptionsBuilderandWriteOptionsBuilder
Articles & Tutorials
- JSON, TOON, and Java Format Libraries — Baeldung tutorial covering JSON, JSON5, and TOON serialization with json-io
| Bundling | JPMS & OSGi |
| Java | JDK 1.8+ (multi-release JAR with module-info.class) |
| Package | com.cedarsoftware.io |
API — Static methods on JsonIo: toJson(), toJava(), toMaps(), toToon(), fromToon(), formatJson(), deepCopy()
Configure via ReadOptionsBuilder and WriteOptionsBuilder. Use ClassFactory for difficult-to-instantiate classes.
json-io uses java.util.logging to minimize dependencies. See the user guide to route logs to SLF4J or Log4j 2.
For useful Java utilities, check out java-util
