Skip to content

jdereg/json-io

Repository files navigation

json-io infographic - JSON5, TOON, and Core Capabilities

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.

Quick Start

// 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());

Installation

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>

Latest version: Maven Central


JSON

Usage

// 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>>(){});

Standard JSON Mode — Jackson Compatible

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.Datejava.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.

json-io vs Jackson vs Gson

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).

Key Features

  • Jackson-compatible standard JSONwriteOptions.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 @JsonIdentityInfo on 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} with stringifyMapKeys(true)
  • Extensive configuration via ReadOptionsBuilder and WriteOptionsBuilder
  • Parse JSON with unknown $type references 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.

Reading JSON5

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.

Writing JSON5

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 keysMap<Long, V> writes {100: value} instead of $keys/$items
  • No type infoshowTypeInfoNever() + 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,c or 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: Denver flatten 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

Usage

// 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.

vs JToon

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
    }
}

Spring AI Integration

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

Annotation Support

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.

Supported Types (60+ built-in)

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.

Key Features

  • Fully compatible with both JPMS and OSGi environments
  • Zero external dependencies (other than java-util)
  • Lightweight (json-io.jar is ~330K, java-util is ~700K)
  • Compatible with JDK 1.8 through JDK 24
  • The library is built with the -parameters compiler 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 ReadOptionsBuilder and WriteOptionsBuilder

Documentation

Articles & Tutorials

Release Maven Central

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.

Logging

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

About

Convert Java to JSON/TOON and back. Supports complex object graphs, cyclic references, and TOON format for 40-50% LLM token savings

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors