user-guide-writeOptions.md
May 18, 2026 · View on GitHub
Controlling the output JSON using WriteOptions
Create a new WriteOptions instance and turn various features on/off using the methods below. Example:
WriteOptions writeOptions = new WriteOptionsBuilder().prettyPrint(true).writeLongsAsStrings(true).build();
JsonIo.toJson(root, writeOptions);
To pass these to JsonIo.toJson(root, writeOptions) set up a WriteOptions using the WriteOptionsBuilder.
You can view the Javadoc on the WriteOptionsBuilder class for detailed information. The WriteOptions are created
and made read-only by calling the .build() method on the WriteOptionsBuilder. You can have multiple WriteOptions
instances for different scenarios, and safely re-use them once built (read-only). A WriteOptions instance can be
created from another WriteOptions instance by using new WriteOptionsBuilder(writeOptionsToCopyFrom).
WriteOptions also drives the streaming-write API
WriteOptions is consumed by both serialization surfaces:
JsonIo.toJson(root, writeOptions)— the tree-walking serializer (JsonWriter), which honors the full option set described in this guide (type-info policy, cycle support, custom writers, excluded fields, naming strategies, etc.).JsonIo.createGenerator(out, writeOptions)— the streaming-write cursor API (JsonGenerator), which honors only the token-level options that apply outside the context of walking a Java object graph:prettyPrint,indentationSize,json5UnquotedKeys,json5SmartQuotes,allowNanAndInfinity, andmaxStringLength. Tree-only options (showTypeInfo,cycleSupport, custom writers, field filters, naming strategies, etc.) have no meaning at the streaming level and are ignored.
Pass a null WriteOptions to either API to use cached library defaults — the streaming-generator factory in particular caches the default-options snapshot (lazy-init) for the hot-path zero-config use case.
Constructors
The ClassLoader in the WriteOptionsBuilder is utilized to convert String class names into Class instances.
This feature allows dynamic class loading during the serialization process, ensuring that the correct class types are
associated with the serialized data.
Create new WriteOptions instances.
new WriteOptionsBuilder().feature1().feature2(args).build()
- Start with default options and turn on feature1 and feature2 (which requires an argument)
new WriteOptionsBuilder(
WriteOptions other)
- Copy all the settings from the passed in 'other'
WriteOptions.
In the descriptions below, the first set of APIs listed are the "getter" (read) methods used to retrieve settings from
the WriteOptions. The methods listed subsequently are the "setter" APIs on the WriteOptionsBuilder, which are used
to configure and activate various options.
Additionally, the WriteOptionsBuilder "setter" APIs are designed to return the WriteOptionsBuilder itself,
facilitating chained method calls for streamlined configuration. This chaining allows for the concise and fluent setting
of multiple options in a single statement.
ClassLoader
The ClassLoader in the WriteOptionsBuilder is utilized to convert String class names into Class instances. This
functionality is essential for dynamically loading classes during the serialization process, ensuring that the
appropriate class types are used based on the class names specified in the options.
ClassLoadergetClassLoader()
- Returns the ClassLoader to resolve String class names.
WriteOptionsBuilderclassLoader(ClassLoader loader)
- Sets the ClassLoader to resolve String class names.
Standard JSON Output — standardJson()
The standardJson() convenience method configures json-io to produce standard JSON output that is interoperable
with Jackson and other mainstream JSON libraries. It sets the planned json-io 5.0.0 defaults in a single call:
WriteOptions options = new WriteOptionsBuilder()
.standardJson()
.build();
This enables:
showTypeInfoNever()— no@typemetadata in outputshowRootTypeInfo(false)— no@typeon the root objectcycleSupport(false)— no@id/@refcycle tracking (~35-40% faster)stringifyMapKeys(true)—Map<Long, V>writes{"100": value}instead of@keys/@itemswriteOptionalAsObject(false)—Optional.of(x)writes as barex;Optional.empty()writes asnull(Jackson-compatible primitive form)preserveLeafContainerIdentity(false)—List<String>/Map<UUID, Date>/byte[]etc. written as values, not tracked with@id/@ref(Jackson-aligned)isoDateFormat()—java.util.Date/java.sql.Datewritten as ISO-8601 strings instead of epoch-millis longs (matches Spring Boot's Jackson default)useMetaPrefixDollar()— uses$prefix for any remaining metadata (e.g.,$keysfor POJO key fallback)
The resulting JSON is byte-compatible with what Jackson (with JavaTimeModule and the Spring Boot
default of WRITE_DATES_AS_TIMESTAMPS=false) produces for the same objects — POJOs, Lists, Maps
with String/numeric/UUID/Enum keys, Optional values, and dates. java.time.* types
(Instant, LocalDate, LocalDateTime, ZonedDateTime, OffsetDateTime) are already ISO-8601
by default regardless of this flag. Individual settings can be overridden after the call:
// standardJson() defaults, but keep cycle support on
WriteOptions options = new WriteOptionsBuilder()
.standardJson()
.cycleSupport(true) // override just this one setting
.build();
WriteOptionsBuilderstandardJson()
- Configures all settings to produce standard, interoperable JSON output. These will become the defaults in json-io 5.0.0. Chainable — individual settings can be overridden after this call.
Global Naming Strategy
When migrating a Jackson codebase that uses ObjectMapper.setPropertyNamingStrategy(...), you want a single setting that retargets every DTO to snake_case / kebab-case / UpperCamelCase / etc. without touching any classes. WriteOptionsBuilder.namingStrategy(...) is the direct equivalent.
Priority at field-name resolution time (most specific wins):
- Per-field
@IoProperty("custom")/@JsonProperty("custom") - Per-class
@IoNaming(...)/@JsonNaming(...) - Global
namingStrategy(...)(this option) - Java field name as-is (default —
firstName→"firstName")
Existing annotations are always honored — turning on a global strategy never overrides a class or field that is already explicit about its output name.
Available strategies (IoNaming.Strategy):
| Strategy | firstName → | parseXMLDocument → |
|---|---|---|
SNAKE_CASE | first_name | parse_xml_document |
UPPER_SNAKE_CASE | FIRST_NAME | PARSE_XML_DOCUMENT |
KEBAB_CASE | first-name | parse-xml-document |
UPPER_CAMEL_CASE | FirstName | ParseXMLDocument |
LOWER_DOT_CASE | first.name | parse.xml.document |
LOWER_CASE | firstname | parsexmldocument |
These mirror Jackson's PropertyNamingStrategies so a Jackson DTO annotated @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) keeps working unchanged under json-io, and a Jackson caller that just sets setPropertyNamingStrategy(SnakeCaseStrategy.INSTANCE) globally can use WriteOptionsBuilder.namingStrategy(IoNaming.Strategy.SNAKE_CASE) instead.
IoNaming.StrategygetNamingStrategy()
- Returns the configured global strategy, or
nullif no global strategy is set (the default).
WriteOptionsBuildernamingStrategy(IoNaming.Strategy strategy)
- Sets the global naming strategy. Pass
nullto clear it.
static voidWriteOptionsBuilder.addPermanentNamingStrategy(IoNaming.Strategy strategy)
- Sets a JVM-lifetime default so all
WriteOptionscreated after this call inherit the strategy unless they override it vianamingStrategy(...).
Note: Neither standardJson() nor json5() sets a strategy. Jackson's own default is LowerCamelCase (which matches Java field names and json-io's default), so forcing a strategy into those presets would impose a user-specific choice.
Example:
WriteOptions wo = new WriteOptionsBuilder()
.showTypeInfoNever()
.namingStrategy(IoNaming.Strategy.SNAKE_CASE)
.build();
class Profile {
String firstName = "Alice";
int loginCount = 42;
@IoProperty("uid")
String userId = "u-1"; // @IoProperty wins: still "uid"
}
// {"first_name":"Alice","login_count":42,"uid":"u-1"}
String json = JsonIo.toJson(new Profile(), wo);
Round-tripping Jackson-style snake_case back into a typed POJO works with either a per-class @IoNaming(IoNaming.Strategy.SNAKE_CASE) annotation (the reader then accepts both forms as aliases) or explicit @IoAlias("first_name") / @JsonAlias("first_name") on each field. The global write-side strategy does not register read-side aliases on its own.
Cycle Support - Controlling @id/@ref Generation
By default, json-io performs a two-pass serialization: first tracing references to identify objects that appear multiple times, then writing the JSON with @id/@ref markers. This enables full cycle and shared-reference support but has some overhead.
If your data is acyclic (no circular references) and you don't need shared-reference preservation, you can disable cycle support for significant performance improvement (~35-40% faster writes).
booleanisCycleSupport()
- Returns
trueif cycle support is enabled. Whentrue, the writer traces all references before writing to detect multi-referenced objects, emitting@idon first occurrence and@refon subsequent occurrences.
WriteOptionsBuildercycleSupport(boolean enable)
- Controls whether the
traceReferences()pre-pass runs before serialization.true(default for JSON): Full cycle support -@id/@refemitted for multi-referenced objects, cycles handled correctlyfalse(default for TOON): Skip traceReferences for performance - identity is not preserved and true cycles throwJsonIoExceptionwith guidance to enablecycleSupport(true)
Note: When null is passed for WriteOptions to JsonIo.toToon(), the default is cycleSupport(false) since TOON targets LLM communication where data is typically acyclic.
Behavior Comparison
| Scenario | cycleSupport=true (default) | cycleSupport=false |
|---|---|---|
| Acyclic data | Works correctly, with overhead | Works correctly, faster |
| Object referenced multiple times | @id on first, @ref on subsequent | Written as repeated full values (no @id/@ref) |
| Circular reference (A → B → A) | Handled with @id/@ref | Throws JsonIoException (hint: enable cycleSupport(true)) |
| Performance | ~1.0x baseline | ~1.35-1.40x faster |
Comparison with Other Libraries
| Library | Cycle Handling |
|---|---|
| Jackson | Requires @JsonIdentityInfo annotation on classes; throws exception if unannotated cycles |
| GSON | No built-in support; throws StackOverflowError on cycles |
| json-io (cycleSupport=true) | Automatic - no annotations needed, full graph fidelity preserved |
| json-io (cycleSupport=false) | Skip overhead for acyclic data; throws if a true cycle is encountered |
Example Usage
// Default: Full cycle support (safe for any object graph)
WriteOptions defaultOptions = new WriteOptionsBuilder().build();
String json = JsonIo.toJson(objectGraph, defaultOptions);
// Performance mode: Skip cycle detection for acyclic data
WriteOptions fastOptions = new WriteOptionsBuilder()
.cycleSupport(false)
.build();
String json = JsonIo.toJson(acyclicData, fastOptions);
When to use cycleSupport(false):
- Serializing DTOs, POJOs, or data transfer objects with no circular references
- High-throughput scenarios where performance is critical
- Data structures that are known to be tree-shaped (no shared nodes)
When to keep cycleSupport(true) (default):
- Object graphs with circular references (parent ↔ child relationships)
- Graphs where the same object is intentionally shared across multiple locations
- When you need exact object identity preserved on round-trip
MetaKeys - @id, @ref, @type, @items, @keys, @values
json-io utilizes several special fields added to JSON objects to aid in accurate deserialization:
- @id - When an object is referenced multiple times within the JSON (e.g., across fields, array elements, collection elements, or map keys/values), its first occurrence is tagged with an
@idand a unique identifier (n). This tagging facilitates referencing the object in subsequent occurrences without redundancy. - @ref:n - Subsequent references to an object initially tagged with an
@idare marked with@refand the same identifier (n). This approach greatly reduces JSON size because the object is serialized only once, and subsequent mentions are handled through references. - @type - Specifies the class of the object being serialized, which helps
json-iodetermine the correct class to instantiate during deserialization. - @items, @keys, @values - These keys are used to structure collections and maps in JSON, ensuring that the original data structure's integrity is maintained during serialization and deserialization.
These meta-keys enhance the efficiency of the JSON format by supporting circular references (e.g., A => B => C => A), reducing memory usage, and preserving the original object graph's structure through serialization.
booleanisShortMetaKeys()
- Returns
trueif instructed to use short meta-keys (@i => @id, @r => @ref, @t => @type, @k => @keys, @v => @values, @e => @items),falsefor full size.falseis the default.
WriteOptionsBuildershortMetaKeys(boolean shortMetaKeys)
- Sets to boolean
trueto turn on short meta-keys,falsefor long.
Meta Key Prefix Override
By default, json-io uses @ prefix for meta keys in standard JSON mode (e.g., "@type", "@id") and $ prefix in JSON5 mode (e.g., $type:, $id:). The $ prefix is preferred in JSON5 because it's a valid ECMAScript identifier character, allowing keys to be unquoted.
You can override this default behavior to force a specific prefix regardless of the JSON mode:
CharactergetMetaPrefixOverride()
- Returns the meta key prefix override character, or
nullif using default behavior.'@'forces @ prefix,'$'forces $ prefix.
WriteOptionsBuilderuseMetaPrefixAt()
- Forces the use of
@prefix for all meta keys (e.g.,"@type","@id","@ref"), even in JSON5 mode. The@prefix always requires quotes since@is not a valid identifier start character.
WriteOptionsBuilderuseMetaPrefixDollar()
- Forces the use of
$prefix for all meta keys (e.g.,"$type","$id","$ref"), even in standard JSON mode. When used in standard JSON mode, the keys will still be quoted ("$type":) since unquoted keys require JSON5 mode to be enabled.
Use Cases
- Interoperability: When communicating with systems that expect a specific meta key prefix, use these methods to ensure compatibility.
- JSON Schema alignment: The
$prefix has precedent in JSON Schema ($schema,$id,$ref), so you may prefer it for consistency. - Migration: When transitioning between JSON and JSON5 formats, you can maintain prefix consistency across both modes.
Example Usage
// Force @ prefix even in JSON5 mode (keys will be quoted)
WriteOptions options = new WriteOptionsBuilder()
.json5()
.useMetaPrefixAt() // Output: {"@type":"...", ...} instead of {$type:"...", ...}
.build();
// Force $ prefix in standard JSON mode (keys will be quoted)
WriteOptions options = new WriteOptionsBuilder()
.useMetaPrefixDollar() // Output: {"$type":"...", ...} instead of {"@type":"...", ...}
.build();
Note: When reading JSON, json-io accepts all meta key variants (@type, @t, $type, $t, quoted or unquoted) regardless of which format was used to write the JSON. This ensures full backward and forward compatibility.
Aliasing - Shorten Class Names in @type
Aliasing is a feature in json-io that simplifies JSON output by converting fully qualified Java class names into shorter, simpler class names. For example, java.util.ArrayList is aliased to just ArrayList, making the JSON more compact and readable.
- Default Aliases: By default,
json-ioincludes aliases for many common JDK classes to reduce the JSON content size automatically. - Adding Custom Aliases: You can add custom aliases for your classes within the application. For instance, adding an alias from
com.mycompany.FootoFoowill also automatically generate aliases for array types such asFoo[],Foo[][], andFoo[][][], ensuring consistency across all usages of the class in JSON. - Scope of Aliases: The aliases added affect only the instance of
WriteOptionscreated from aWriteOptionsBuilder. To apply aliases across all instances throughout the JVM's lifecycle, refer to the application-scoped options section. - External Alias Configuration: Alternatively, you can manage aliases by creating an aliases.txt file and placing
it in the class path.
json-ioprovides a comprehensive default list, but you can override this by providing your own file. - Annotation Alternative: For your own classes, use
@IoTypeName("ShortName")directly on the class. See Annotations.
StringgetTypeNameAlias(String typeName)
- Alias Type Names, e.g. "ArrayList" instead of "java.util.ArrayList".
Map<String, String>aliases()
- Returns
Map<String, String>containing all String class names to alias names.
WriteOptionsBuilderaliasTypeNames(Map<String, String> aliasTypeNames)
- Puts the
Mapcontaining String class names to alias names. The passed inMapwill beputAll()copied overwriting any entries that match values in the passed in Map. New entries in the Map are added.
WriteOptionsBuilderaliasTypeName(String typeName, String alias)
- Sets the alias for a given class name.
WriteOptionsBuilderaliasTypeName(Class, String alias)
- Sets the alias for a given class.
WriteOptionsBuilderremoveAliasTypeNamesMatching(String typeNamePattern)
- Remove alias entries from this
WriteOptionsBuilderinstance where the Java fully qualified string class name matches the passed in wildcard pattern. ThetypeNamePatternmatches using a wild-card pattern, where * matches anything and ? matches one character. As many * or ? can be used as needed.
@type
The @type field in json-io is used to provide hints to json-io about which classes to instantiate. Typically, json-io is able to automatically determine the Java type (class) of an object based on the field type of object, the component type of array, or if a root class is explicitly specified. However, there are scenarios where the type cannot be directly inferred.
For example, consider a field in a class declared as an Object. If more complex, derived instances are assigned to
this Object field, json-io needs additional information to correctly handle serialization and deserialization.
In such cases, json-io includes an @type=typename entry in the JSON object to specify the exact class type that should be instantiated. This ensures that json-io knows to instantiate and populate the specific class with the data from the input JSON.
This mechanism is crucial for maintaining the fidelity of the object graph when the field types are not concrete or are too generic to determine directly, enabling accurate and efficient JSON processing.
booleanisAlwaysShowingType()
- Returns
trueif set to always show type (@type).
booleanisNeverShowingType()
- Returns
trueif set to never show type (no @type).
booleanisMinimalShowingType()
- Returns
trueif set to show minimal type (@type). This is the default. Note: Returnstruefor both MINIMAL and MINIMAL_PLUS modes since MINIMAL_PLUS is a superset of MINIMAL.
booleanisMinimalPlusShowingType()
- Returns
trueif set to show minimal plus type info.
WriteOptionsBuildershowTypeInfoAlways()
- Sets to always show type.
WriteOptionsBuildershowTypeInfoNever()
- Sets to never show type.
WriteOptionsBuildershowTypeInfoMinimal()
- Sets to show minimal type. This means that when the type of object can be inferred, a type field will not be output. This is the default.
WriteOptionsBuildershowTypeInfoMinimalPlus()
- Sets to show minimal plus type info. This extends minimal mode by also omitting
@typefor Collections and Maps whose runtime type is the "natural default" for the field's declared type. For example, an ArrayList in a List field writes as direct[...]instead of{"@type":"ArrayList","@items":[...]}. Natural defaults include: List→ArrayList, Set→LinkedHashSet, Map→LinkedHashMap, SortedSet→TreeSet, SortedMap→TreeMap, etc. Also omits@typefor convertible types (types that can be losslessly round-tripped via String representation like ZonedDateTime, UUID, BigDecimal, etc.).
Root @type
The @type field on the root object can be controlled when using showTypeInfoMinimal() or showTypeInfoMinimalPlus(). Since json-io now supports .asClass() and .asType() on the read side to specify the expected root type, the @type on the root object may be redundant when the receiver knows what type to expect.
booleanisShowingRootTypeInfo()
- Returns
trueif the root type (@type on the root object) should be shown,falseto omit it.
WriteOptionsBuildershowRootTypeInfo()
- Show the
@typeon the root object. This is the current default behavior. Use this method to explicitly enable root type output, especially after the default changes to omit root type in a future release.- Only valid with
showTypeInfoMinimal()orshowTypeInfoMinimalPlus(). ThrowsIllegalStateExceptionif used withshowTypeInfoAlways()orshowTypeInfoNever().
WriteOptionsBuilderomitRootTypeInfo()
- Omit the
@typeon the root object in the JSON output. This is useful when the receiving system will specify the expected type via.asClass()or.asType(), making the root@typeredundant. Omitting the root type reduces JSON payload size.- Only valid with
showTypeInfoMinimal()orshowTypeInfoMinimalPlus(). ThrowsIllegalStateExceptionif used withshowTypeInfoAlways()orshowTypeInfoNever().
Note: The current default is to show the root type (for backward compatibility), but this default will likely change to omit root type in a future release.
Validation Rules:
showTypeInfoAlways()— always show@typeon all objects, root type control not allowedshowTypeInfoNever()— never show@typeon any object, root type control not allowedshowTypeInfoMinimal()(default) — allowsshowRootTypeInfo()/omitRootTypeInfo()for fine-grained controlshowTypeInfoMinimalPlus()— allowsshowRootTypeInfo()/omitRootTypeInfo()for fine-grained control
Pretty Print
To generate more readable JSON output with multiple lines and indentations, enable the pretty-print feature in
json-io. This feature formats the JSON output to be visually structured, which is particularly helpful for debugging
or when viewing the JSON data directly. Conversely, if compactness is preferred, especially for network transmission or
storage efficiency, you can disable pretty printing to produce a single-line, unindented JSON output.
To activate pretty printing, configure your serialization settings accordingly:
booleanisPrettyPrint()
- Returns the pretty-print setting,
truebeing on, using lots of vertical white-space and indentations,falsewill output JSON in one line. The default isfalse.
WriteOptionsBuilderprettyPrint(boolean prettyPrint)
- Sets the 'prettyPrint' setting,
trueto turn on,falsewill turn off. The default setting isfalse.
Pretty-print is honored by both the tree writer (JsonIo.toJson(...)) and the streaming-write API (JsonIo.createGenerator(...)). The generator emits a newline + indentationSize × depth spaces before each value-position and before each matching close-brace/bracket; empty containers ({}, []) stay compact.
lruSize - LRU Size [Cache of fields and filters]
Set the maximum number of Class to Field mappings and Class to accessor mappings. This will allow infrequently used Class's
to drop from the cache - they will be dynamically added back if not in the cache. Reduces operational memory foot print.
intlruSize()
- Return the LRU size
WriteOptionsBuilderlruSize(int size)
- Set the max LRU cache size
Automatically Close OutputStream (or Not)
In json-io, you have the option to automatically close the OutputStream after writing JSON output or to leave it
open for further writing. This feature is particularly useful in scenarios where multiple JSON objects need to be written
sequentially to the same stream.
Example Use Case: NDJSON Format
NDJSON (Newline Delimited JSON) is a format where multiple JSON objects are separated by newlines ({...}\n{...}\n{...}). To efficiently create NDJSON, you might prefer not to close the stream after each JSON object is written, allowing continuous addition to the stream:
WriteOptions options = new WriteOptionsBuilder().closeStream(false).build();
OutputStream outputStream = new FileOutputStream("output.ndjson");
try {
JsonIo.toJson(outputStream, object1, options);
JsonIo.toJson(outputStream, object2, options);
// Continue writing more JSON objects
} finally {
outputStream.close(); // Ensure the stream is closed after all operations are complete
}
Benefits
This setup ensures that the OutputStream remains open for additional writes, making it ideal for formats like NDJSON or when batch processing multiple JSON outputs. Refer to the user guide's starting section for a comprehensive example and additional guidance.
booleanisCloseStream()
- Returns
trueif set to automatically close stream after write (the default), orfalseto leave stream open after writing to it.
WriteOptionsBuildercloseStream(boolean closeStream)
- Sets the 'closeStream' setting,
trueto turn on,falsewill turn off. The default setting istrue.
Long as String
When dealing with large numerical values, especially those ranging between 17 to 19 digits, it's important to consider the limitations of JavaScript's number handling. JavaScript internally uses the Double format (IEEE 754) for numbers, which may not accurately represent long integers within this range due to precision limitations.
Why Output as String?
To ensure that these large Long values are accurately conveyed and processed in JavaScript environments, json-io provides the option to serialize Long values as strings. This approach preserves the full precision of the data when transmitted to JavaScript, allowing for correct display and usage without loss of fidelity.
Default Behavior
By default, this feature is turned off. To enable the output of Long values as strings, you need to adjust your serialization settings:
booleanisWriteLongsAsStrings()
- Returns
trueindicating longs will be written as Strings,falseto write them out as native JSON numbers.
WriteOptionsBuilderwriteLongsAsStrings(boolean writeLongsAsStrings)
- Set to boolean
trueto turn on writing longs as Strings,falseto write them as native JSON longs. The default setting isfalse.This feature is important to marshal JSON with large long values (18 to 19 digits) to Javascript. Long/long values are represented in Javascript by storing them in aDoubleinternally, which cannot represent a fulllongvalue. Using this feature allows longs to be sent to Javascript with all their precision, however, they will be Strings when received in the Javascript. This will let you display them correctly, for example.
null Field Values
In json-io, you have the option to configure how null values are handled during serialization. By default, fields with null values are included in the JSON output. However, you can change this setting to exclude null values, which can significantly reduce the size of the JSON output for applications where null fields are not necessary.
Configuring null Value Exclusion
To exclude null values from the JSON output, you need to activate this setting in the WriteOptions. This can be particularly beneficial in scenarios where minimizing the JSON size is critical or where the presence of null values offers no additional value:
booleanisSkipNullFields()
- Returns
trueindicating fields with null values will not be written,falsewill still output the field with an associated null value. The default isfalse.
WriteOptionsBuilderskipNullFields(boolean skipNullFields)
- Sets the boolean where
trueindicates fields with null values will not be written to the JSON,falsewill allow the field to still be written.
Map Output Format
json-io provides flexible serialization options for Java Map instances, accommodating different types of keys.
Default Handling of Map Keys
- String Keys: If all keys in the
MapareStrings,json-ioserializes theMapas a standard JSON object{},using theMap's,keys as the keys in the JSON object. - Object Keys: When the keys are not all strings (e.g., they are objects),
json-iohandles this by serializing the keys and values separately. The keys are placed in an array under an@keystag, and the corresponding values are listed in an@valuesarray. This ensures that the structure and associations within theMapare preserved.
Stringify Map Keys
When enabled, non-String map keys that have a bidirectional String conversion via Converter are written as
stringified keys in a standard JSON object instead of the @keys/@items parallel-array format. This produces
output identical to Jackson and other mainstream JSON libraries.
Supported key types include Long, Integer, Double, Boolean, BigDecimal, BigInteger, UUID, Date,
ZonedDateTime, Enum, Character, AtomicLong, and any type with a registered bidirectional Converter to/from
String. Complex POJO keys that lack a bidirectional String conversion still use @keys/@items as a fallback.
Example:
// Without stringifyMapKeys (default for JSON):
// {"@keys":[100,200], "@items":["alpha","beta"]}
// With stringifyMapKeys:
// {"100":"alpha","200":"beta"}
booleanisStringifyMapKeys()
- Returns
trueif non-String map keys with bidirectional String conversions will be stringified and written as regular JSON object keys. Default isfalsefor JSON,truefor JSON5.
WriteOptionsBuilderstringifyMapKeys(boolean stringifyMapKeys)
- When
true, non-String map keys that can be converted to/from String viaConverterare written as stringified keys in standard JSON object format. Whenfalse, non-String keys use@keys/@items.forceMapOutputAsTwoArrays(true)overrides this setting.
Note: The read side automatically handles both formats. When the declared field type provides generic type info
(e.g., Map<Long, String>), string keys like "100" are converted to the declared key type via Converter. This
capability has been available since json-io 4.94.0.
Force Two-Array Format
If you prefer a consistent format for all Map instances, regardless of the key types, you can enable a setting to
always serialize Maps using the @keys:[], @values:[] format. This option overrides both the default String-key
handling and stringifyMapKeys:
booleanisForceMapOutputAsTwoArrays()
- Returns
trueif set to force Java Maps to be written out as two parallel arrays, once for keys, one array for values. The default isfalse.
WriteOptionsBuilderforceMapOutputAsTwoArrays(boolean forceMapOutputAsTwoArrays)
- Sets the boolean 'forceMapOutputAsTwoArrays' setting. If Map's have String keys they are written as normal JSON objects. With this setting enabled, Maps are written as two parallel arrays. This overrides
stringifyMapKeys.
Floating Point Options
Handling special floating point values such as NaN (Not a Number) and Infinity (+Inf/-Inf) in JSON can be tricky since the JSON specification does not officially support these values. However, json-io offers a feature that allows these values to be serialized and deserialized correctly, although this feature is disabled by default.
Considerations for Enabling this Feature:
- Compatibility: While enabling the serialization of NaN and Infinity can be useful for applications that understand and can process these values, be cautious as other systems and APIs might not support them, leading to potential interoperability issues.
booleanisAllowNanAndInfinity
- Returns
trueif set to allow serialization ofNaNandInfinityfordoublesandfloats.
WriteOptionsBuilderallowNanAndInfinity(boolean allow)
- true will allow
doublesandfloatsto be output asNaNandINFINITY,falseand these values will come across asnull.
Optional, OptionalInt, OptionalLong, OptionalDouble Output
By default, json-io writes java.util.Optional values (and the three primitive
variants OptionalInt, OptionalLong, OptionalDouble) in the same primitive
form that Jackson (with Jdk8Module) and other mainstream JSON libraries use:
Optional.empty()→nullOptional.of(value)→ the barevalue(no{...}wrapping)OptionalInt.of(42)→42OptionalDouble.of(3.14)→3.14
This produces standard JSON that any other JSON library can consume. The same behavior is used for JSON5 and TOON output — they never emitted the legacy form, so no toggle applies there.
On the read side, json-io accepts both the primitive form (for
interoperability with Jackson/Gson and for JSON produced by json-io 4.101.0+)
and the legacy object form {"present":true,"value":X} (for JSON produced
by older json-io versions). Field-type-aware coercion wraps scalars and nulls
into the appropriate Optional variant based on the declared field type.
Legacy Object Form
If you need to interoperate with pre-4.101.0 json-io readers that do not
understand the primitive form, enable writeOptionalAsObject(true). json-io
will then emit the legacy form:
Optional.empty()→{"present":false}Optional.of("hello")→{"present":true,"value":"hello"}
booleanisWriteOptionalAsObject()
- Returns
trueifOptional*values are written in the legacy object form,false(default) if they are written in Jackson-compatible primitive form.
WriteOptionsBuilderwriteOptionalAsObject(boolean writeOptionalAsObject)
trueemits the legacy{"present":X,"value":Y}form.false(default) emits Jackson-compatible primitive form.standardJson()always resets this tofalse.
Example:
// Default — Jackson-compatible primitive form:
WriteOptions opts = new WriteOptionsBuilder().build();
String json = JsonIo.toJson(pojoWithOptionalFields, opts);
// → {"name":"Alice","middleName":null,"age":30}
// Legacy form — only needed for pre-4.101.0 readers:
WriteOptions legacyOpts = new WriteOptionsBuilder()
.writeOptionalAsObject(true)
.build();
String json2 = JsonIo.toJson(pojoWithOptionalFields, legacyOpts);
// → {"name":{"present":true,"value":"Alice"},"middleName":{"present":false},...}
Leaf-Container Identity — preserveLeafContainerIdentity (4.101.0)
What it controls: whether json-io traces shared-identity for containers whose
declared element types are all non-referenceable leaves — List<String>,
Map<UUID, Date>, byte[], String[], Map<Long, BigDecimal>, List<MyEnum>, etc.
Default (false) — each reference to a leaf-element container is serialized
independently. Two fields pointing to the same List<String> become two
independent copies on the wire and on read-back. Matches Jackson's default
identity semantics and aligns with json-io's convergence toward standard JSON
output.
Opt-in (true) — json-io emits @id/@ref for leaf-element containers that
are shared across fields. Matches json-io's pre-4.101.0 behavior.
Containers holding POJO elements (List<Foo>, Map<String, Foo>, Foo[])
always have identity traced regardless of this flag. This option only governs
leaf-element containers, and only affects writes with cycleSupport=true — when
cycle support is off, reference tracing does not run.
booleanisPreserveLeafContainerIdentity()
- Returns
trueif shared identity is preserved for leaf-element containers,false(default as of 4.101.0) if each reference is serialized independently.
WriteOptionsBuilderpreserveLeafContainerIdentity(boolean preserveLeafContainerIdentity)
trueemits@id/@reffor sharedList<String>,Map<UUID, Date>,byte[], etc.false(default) writes each reference as an independent copy.standardJson()always resets this tofalse.
static voidaddPermanentPreserveLeafContainerIdentity(boolean preserveLeafContainerIdentity)
- Set a process-wide default. All
WriteOptionsinstances created afterwards will initialize with this value unless explicitly overridden. Useful for apps that must interoperate with pre-4.101.0 readers and want the legacy behavior without annotating every call site.
Example:
// Default (Jackson-aligned): shared List<String> serialized as two copies
class Foo {
List<String> list1;
List<String> list2;
}
Foo f = new Foo();
List<String> shared = Arrays.asList("a", "b");
f.list1 = shared;
f.list2 = shared;
String json = JsonIo.toJson(f);
// → {"@type":"Foo","list1":["a","b"],"list2":["a","b"]}
// On read-back: f.list1 != f.list2 (different instances, same content)
// Opt-in: preserve shared identity (legacy behavior)
WriteOptions opts = new WriteOptionsBuilder()
.preserveLeafContainerIdentity(true)
.build();
String json2 = JsonIo.toJson(f, opts);
// → {"@type":"Foo","list1":{"@id":1,"@items":["a","b"]},"list2":{"@ref":1}}
// On read-back: f.list1 == f.list2 (shared instance preserved)
When to opt in:
- Round-trip fidelity with pre-4.101.0 json-io JSON where shared leaf-element containers carried
@id/@ref - Application code relies on
list1 == list2after deserialization of primitive-element containers - Shared leaf-containers hold significant memory and copy-duplication is undesirable
Why it's off by default:
- Standard JSON interop — Jackson, Gson, and other parsers don't track this identity
- Simpler output (less
@id/@refnoise) and faster writes (~10-25% oncycleSupport=truewrite paths, concentrated on Map-heavy payloads) - Aligns with json-io's 5.0 direction to minimize proprietary metadata
Enum Options in json-io
Enums in Java are commonly used as a discrete list of values, but there are instances where additional fields are added to these enums. These fields can be either public or private, depending on the design requirements.
Handling Enum Fields
The WriteOptions of json-io provide a flexible set of configurations that allow developers to customize
how enums and EnumSet objects are serialized into JSON. These options determine whether enums are written
as simple strings, detailed objects with public and private fields, or represented using specific metadata
like @enum or @type. By adjusting these settings, users can balance between backward compatibility and
newer, streamlined serialization formats. Below is a detailed breakdown of the available options and their
effects.
Configuration Example
Here's how you configure these options in json-io:
booleanisWriteEnumAsString()
- Returns
trueif enums are to be written out as Strings (default). If this is false, then enums are being written as objects, and then theisEnumPublicFieldsOnly()API is valid and will indicate if enums are to be written with public/private fields.
booleanisEnumPublicFieldsOnly()
- Returns
trueindicating that only public fields will be output on an enum (default). The default is to only output public fields as well as to write it as a primitive (single value) instead of a JSON { } object when possible. Set tofalseto write public/private fields.
booleanisEnumSetWrittenOldWay()
- Returns
trueifEnumSetis written with@enum=enumElementTypeClass,orfalseifEnumSetsare written with@type=enumElementTypeClass. Default istruefor backward compatibility, but will switch tofalsein an future release.
WriteOptionsBuilderwriteEnumsAsString()
- Sets the option to write enums as a
String.This is the default option. If you have calledwriteEnumAsJsonObject(true or false),callwriteEnumsAsString()to return to enum output asString.
- Top-level enums written as strings omit
@typemetadata unless the enum defines additional fields, in which case the type is included so it can round-trip correctly.
WriteOptionsBuilderwriteEnumAsJsonObject(boolean writePublicFieldsOnly)
- Sets the option to write all the member fields of an enum, using JSON { } format for the enum, to allow for multiple fields. Setting this option to
trueorfalse(include/exclude private fields), turns off the writeEnumsAsString() option. This option is off by default - enums are written asStringby default.
WriteOptionsBuilderwriteEnumSetOldWay(boolean writeEnumSetOldWay)
- Sets the option to write
EnumSetwith@enum=elementTypeClassNameor@type=elementTypeClassName. The default is true (@enum) for backward compatibility. This will change in a future release.
Customizing JSON Output with JsonClassWriter
If you need tailored JSON output for specific Java classes, json-io allows you to author and associate a custom
writer (JsonClassWriter) to any class. This customization can significantly enhance how your data is serialized,
offering precise control over the output format.
How It Works:
- Create a
JsonClassWriter: Implement aJsonClassWriterfor the class whose output you wish to customize. This writer will define how the class is serialized into JSON. - Select Fields and Formatting: Within your custom writer, you have the freedom to select which fields to include and how they should be formatted. This is particularly useful for classes where only certain fields need to be exposed, or where standard serialization does not meet your needs.
- Associate the Writer: Once you've created your
JsonClassWriter, associate it with the class it should serialize.json-iowill then use your custom writer each time it serializes an instance of that class.
Example Implementation:
Here is a simple example of how you might set up a JsonClassWriter:
public class MyCustomWriter implements JsonClassWriter<MyCustomClass> {
@Override
public void write(MyCustomClass obj, boolean showType, Writer output, WriterContext context) throws IOException {
output.write("{");
output.write("\"customField\": \"" + obj.getCustomField() + "\"");
output.write("}");
}
}
// Associate the custom writer with the class
new WriteOptionsBuilder().addCustomWrittenClass(MyCustomClass.class, new MyCustomWriter());
This example shows a custom writer for MyCustomClass that selectively serializes only a specific field. This approach can be adapted to any class to meet your specific serialization needs.
Custom writers vs JsonGenerator
JsonClassWriter is invoked from inside the tree-walking serializer when it encounters an instance of the associated class. The custom writer receives a low-level Writer (for raw char emission) and a WriterContext (for field-write helpers). The WriterContext interface preserves a long-standing convention where some methods (notably writeStringField, writeNumberField, writeArrayFieldStart, etc.) emit a leading comma, so they are safe only for fields after the first inside an existing object body. See the WriterContext Javadoc for the full method contract.
For standalone streaming serialization — building JSON token-by-token without involving the tree walker at all (e.g., porting Jackson JsonGenerator code, large-document streaming, transform pipelines) — use the JsonGenerator streaming-write API. It exposes a Jackson-aligned auto-comma cursor where structural separators are inserted automatically; you don't need to know "is this the first field." JsonGenerator is not the API a JsonClassWriter uses — the two surfaces address different layers of the stack.
If you want JsonGenerator-style auto-comma semantics inside a custom writer today, the cleanest pattern is to delegate to JsonIo.toJson(value, opts) and emit the result via the surrounding Writer, or to wrap fragments with the WriteOptionsBuilder helpers. A future json-io 5.0 release is the candidate moment to unify the two surfaces with a clean auto-comma WriterContext (binary-compat break, opt-out flag for the legacy leading-comma behavior).
JsonClassWritergetCustomWrittenClass(Class)
- Returns a
Mapof Class to custom JsonClassWriter's use to write JSON when the class is encountered during serialization.
booleanisCustomWrittenClass(Class)
- Checks to see if there is a custom writer associated with a given class. Returns
trueif there is,falseotherwise.
WriteOptionsBuildersetCustomWrittenClasses(Map<Class, JsonClassWriter> customWrittenClasses)
- Establishes the passed in
Mapas the complete list of custom writers to be used when writing JSON.
WriteOptionsBuilderaddCustomWrittenClass(Class, JsonClassWriter customWriter)
- Adds a custom writer for a specific Class.
"Not" Customized Class Writers
In json-io, customized writers are typically associated with a specific class and its derivatives. However, there are
situations where the inheritance model might inadvertently cause a class to be handled by a custom writer when this is
not desired. To address this, you can specify classes that should not use customized writers, effectively overriding
the inheritance behavior.
How It Works:
- Priority of "Not" Customized List: Classes added to the "Not" customized list take precedence over those on the
customized list. This ensures that even if a class or its parent is associated with a custom writer, you can exclude it
explicitly, allowing the default
json-ioJSON writer to handle its serialization. - Using the Default Writer: By placing a class on the "Not" customized list,
json-ioreverts to using the standard serialization mechanism for that class, bypassing any custom writer logic that might otherwise apply due to class inheritance.
Benefits:
This feature is particularly useful in complex inheritance structures where you need fine-grained control over serialization behavior, ensuring that certain classes are serialized in a standard, predictable manner regardless of the broader customization strategy.
booleanisNotCustomWrittenClass(Class)
- Checks if a class is on the not-customized list. Returns
trueif it is,falseotherwise.
WriteOptionsBuilderaddNotCustomWrittenClass(Class notCustomClass)
- Adds a class to the not-customized list. This class will use 'default' processing when written. This option is available as custom writers apply to the class and their derivatives. This allows you to shut off customization for a class that is picking it up due to inheritance.
WriteOptionsBuildersetNotCustomWrittenClasses(Collection<Class> notCustomClasses)
- Initializes the list of classes on the non-customized list.
Add Custom Options
In json-io, you have the flexibility to define custom options — key-value pairs that you can associate with specific serialization behaviors. These options are particularly useful for passing additional data or configuration settings to a custom writer, allowing for more dynamic and context-sensitive serialization.
How It Works:
- Defining Custom Options: Custom options are essentially string keys associated with values of your choice. Once defined, these options can be accessed by custom writers during the serialization process, enabling them to adjust their behavior based on the options provided.
- Usage in Custom Writers: When a custom writer is invoked, it can retrieve and utilize these custom options to make decisions about how to serialize particular aspects of an object. This capability is especially useful for implementing advanced serialization logic that depends on runtime conditions or specific application requirements.
WriteOptionsBuilderaddCustomOption(String key, Object value)
- Add the custom key/value pair to your WriteOptions. These will be a available to any custom writers you add. If you add a key with a value of null associated to it, that will remove the custom option.
Included Fields
The "Included Fields" feature in json-io provides a mechanism to selectively control the serialization of fields
within a class. This approach acts as a whitelist, allowing you to specify exactly which fields should be included in
the JSON output for a particular class.
How It Works:
- Field Selection: By specifying fields in the "Included Fields" setting, you dictate that only these fields will be serialized when an instance of the class is processed. This is particularly useful for classes with numerous fields where only a subset is relevant for the JSON output.
- Whitelist Approach: This feature adopts a whitelist approach, ensuring that serialization is limited to only those fields explicitly listed.
Handling Conflicts:
- Precedence Rules: If a field appears in both the "Includes" and "Excludes" lists, the "Excludes" list will take precedence. This ensures that the exclusion rules override the inclusion rules in cases of conflict.
Example Usage:
To configure the included fields for a class, you might set up your serialization options as follows:
WriteOptions options = new WriteOptionsBuilder()
.includeFields(MyClass.class, "field1", "field2")
.build();
String json = JsonIo.toJson(instanceOfMyClass, options);
Benefits:
This feature is particularly useful for:
- Reducing Payload Size: Minimizing the size of the JSON output by excluding unnecessary fields.
- Enhancing Security and Privacy: Limiting the exposure of sensitive data by not serializing it.
- Customizing Output: Tailoring the JSON output to meet specific front-end or API consumption requirements.
Using the "Included Fields" feature effectively allows developers to fine-tune the serialization process, ensuring that only relevant data is included in the JSON output.
Set<String>getIncludedFields(Class)
- Returns a
Setof Strings field names associated to the passed in class to be included in the written JSON.
WriteOptionsBuilderaddIncludedField(Class, String fieldName)
- Adds a single field to be included in the written JSON for a specific class.
WriteOptionsBuilderaddIncludedFields(Class, Collection<String> includedFields)
- Adds a
Collectionof fields to be included in written JSON for a specific class.
WriteOptionsBuilderaddIncludedFields(Map<Class, Collection<String>> includedFields)
- Adds multiple Classes and their associated fields to be included in the written JSON.
Excluded Fields
The "Excluded Fields" feature in json-io offers a way to selectively prevent certain fields from being serialized
in the JSON output. This feature works as a blacklist, where you can specify which fields should be excluded for a
particular class.
How It Works:
- Field Exclusion: By specifying fields in the "Excluded Fields" setting, you ensure that these fields are not included when an instance of the class is serialized. This is effective when a class has many fields but only a few need to be omitted from the JSON output.
- Blacklist Approach: This approach provides a straightforward way to exclude specific fields, which can be
useful for privacy considerations, omitting unnecessary
ClassLoaderfields, or other sensitive data.
Handling Conflicts:
- Precedence Rules: In cases where fields are specified in both the "Includes" and "Excludes" lists, the fields in the "Excludes" list will take precedence. This ensures that exclusion rules override inclusion rules where there is a conflict.
Example Usage:
To configure the excluded fields for a class, you can set up your serialization options like this:
WriteOptions options = new WriteOptionsBuilder()
.excludeFields(MyClass.class, "field3", "field4")
.build();
String json = JsonIo.toJson(instanceOfMyClass, options);
In this setup, field3 and field4 of MyClass will not appear in the JSON output, regardless of how many other fields the class may have.
Benefits:
This feature is particularly valuable for:
- Enhancing Privacy: Ensuring that sensitive data fields are not inadvertently serialized and exposed.
- Reducing Output Clutter: Streamlining the JSON output by removing unnecessary or irrelevant fields.
- Customizing Output: Allowing more control over the JSON output to fit specific data presentation or API requirements.
Utilizing the "Excluded Fields" feature effectively allows developers to manage the serialization of class fields meticulously, focusing on only transmitting necessary information.
Set<String>getExcludedFields(Class)
- Returns a
Setof Strings field names associated to the passed in class to be excluded in the written JSON.
WriteOptionsBuilderaddExcludedField(Class, String excludedField)
- Adds a single field to be excluded from the written JSON for a specific class.
WriteOptionsBuilderaddExcludedFields(Class, Collection<String> excludedFields)
- Adds a
Collectionof fields to be excluded in written JSON for a specific class.
WriteOptionsBuilderaddExcludedFields(Map<Class, Collection<String>> excludedFields)
- Adds multiple Classes and their associated fields to be excluded from the written JSON.
Non-Standard Accessors
The "Non-Standard Accessors" feature in json-io provides the flexibility to define custom accessor methods for
properties in Java objects that do not adhere to the conventional getter/setter naming patterns. This is particularly
useful for interacting with properties where the accessor methods have unique names.
Use Case:
- Custom Accessor Names: Some classes, such as
java.time.Instant, use non-standard methods likegetEpochSecond()to access properties, which do not follow the traditionalgetPropertyName()format. This can pose challenges when these methods need to be used for serializing properties into JSON, especially in environments like Java 17+ where reflective access to private fields is restricted.
Default Accessors:
- JDK Classes: For many JDK classes, json-io has already configured these non-standard accessors by default. For
example, accessors for
java.time.Instantand similar classes are pre-defined, facilitating easier integration and usage without additional configuration.
Benefits:
- Compatibility with Java 17+: Ensures that json-io can continue to function seamlessly with Java versions that enforce stricter encapsulation by using public methods to access property values.
- Flexibility in Serialization: Allows developers to precisely control how properties are accessed and serialized, accommodating various coding styles and requirements. By enabling custom accessors for non-standard method names, developers can enhance the adaptability and robustness of their serialization logic in json-io, ensuring compatibility across different Java versions and compliance with modern encapsulation practices.
Annotation Alternative: Use @IoGetter("fieldName") on your getter method. See Annotations.
Configuring Non-Standard Accessors:
This option allows json-io to recognize and utilize these non-standard method names as accessors during serialization, ensuring that property values can be correctly retrieved and included in the JSON output.
WriteOptionsBuilderaddNonStandardGetter(Class, String fieldName, String methodName)
- Add another field and non-standard method to the Class's list of non-standard accessors. For the example above, use
addNonStandardMapping(Instant.class, "second", "getEpochSecond").
FieldFilters
In json-io, FieldFilters provide a dynamic mechanism for selectively including or excluding fields from serialization based on specific field characteristics rather than just their names. This feature allows you to add or remove custom filters to/from the field filter chain, enhancing control over the serialization process.
How FieldFilters Work:
- Filter Chain: Each
FieldFilterin the chain is applied to a reflected field during the serialization process. The filter determines whether a field should be included in the JSON output based on its characteristics. - Custom Implementation: You can implement your own
FieldFilterto apply specific exclusion criteria. A field that matches the criteria set in the filter will be excluded when the filter returnstrue.
Field Characteristics:
Filters can be designed to recognize and act upon various field characteristics, such as:
transientfinalvolatilepublicprotectedprivate
This allows for highly granular control over which fields are serialized, based on their modifiers or access levels.
Example Filters:
- EnumFieldFilter: This built-in filter can be used as a reference for implementing filters that exclude fields based on their type, such as filtering out enum fields.
- StaticFieldFilter: Another example that excludes all static fields from being serialized.
Implementing a Custom FieldFilter:
Here's how you might define and add a custom FieldFilter:
public class MyCustomFieldFilter implements FieldFilter {
@Override
public boolean shouldExclude(Field field) {
// Exclude fields that are final and volatile
int modifiers = field.getModifiers();
return Modifier.isFinal(modifiers) && Modifier.isVolatile(modifiers);
}
}
// Add the custom filter to json-io
new WriteOptionsBuilder().addFieldFilter("final-volatile", new MyCustomFieldFilter());
This filter will exclude fields that are final volatile. When adding the filter, a name is required. This provides an easy way to identify the filter if you need to remove it.
Benefits:
- Enhanced Flexibility: Allows developers to tailor the serialization process to specific requirements, excluding fields based on a wide range of attributes.
- Increased Security: Enables the exclusion of sensitive fields, such as those marked as private or transient, from the serialization process. By utilizing FieldFilters, developers gain a powerful tool to customize the serialization behavior of json-io, ensuring that only relevant and appropriate data is included in the JSON output.
WriteOptionsBuilderaddFieldFilter(String filterName, FieldFilter filter)
- Add a named
FielFilterto the field filter chain.
WriteOptionsBuilderremoveFieldFilter(String filterName)
- Remove a named
FieldFilterfrom the field filter chain.
Method Filters
json-io allows the customization of serialization behavior through the use of "Method Filters." These filters provide control over which methods are used during serialization, particularly useful for excluding methods that may not be suitable for direct data extraction.
How Method Filters Work:
- Filter Chain: Method Filters are added to a method filter chain within
json-io. Each filter in the chain has the opportunity to inspect a reflected method. - Exclusion Logic: If a filter returns
truefor a method, that method is excluded from being used to access data. Instead,json-iowill revert to direct field access techniques. This is particularly beneficial if a method, such as a getter, triggers undesirable side effects.
Criteria for Filtering:
Filters can be applied based on any aspect of the method's signature or behavior, including:
- Method name
- Owning class
- Visibility (public, private, protected)
- Whether the method is static or non-static
Example Usage:
Here's how you might implement and add a MethodFilter to exclude all static methods from being used in serialization:
public class StaticMethodFilter implements MethodFilter {
@Override
public boolean shouldExclude(Method method) {
// Exclude static methods
return Modifier.isStatic(method.getModifiers());
}
}
// Add the method filter to json-io
new WriteOptionsBuilder().addMethodFilter("static", new StaticMethodFilter());
This filter excludes all static methods, forcing json-io to use instance fields or non-static methods for serialization.
Benefits:
- Control Over Serialization: Provides granular control over the serialization process, allowing exclusion of methods based on specific criteria.
- Avoidance of Side Effects: Prevents the use of methods that might modify the state or trigger behaviors unsuitable during serialization.
- Flexibility: Adapts serialization to the specific needs and constraints of your application, ensuring data is accessed in the most appropriate manner.
By using Method Filters, developers can fine-tune how json-io accesses data during serialization, enhancing the reliability and predictability of the output JSON.
WriteOptionsBuilderaddMethodFilter(String filterName, MethodFilter filter)
- Add a named
MethodFilterfilter to the method filter chain. Write a subclass ofMethodFilterand add it to theWriteOptionsBuilderusing this method, or use theWriteOptionsBuilder.addPermanent*()APIs to install it as default in all createdWriteOptions.
WriteOptionsBuilderaddNamedMethodFilter(String filterName, Class name, String methodName)
- Add a
NamedMethodFilterfilter to the method filter chain. You supply the Class name and the String name of the accessor (getter), and it will create a NamedMethodFilter for you, and add it to the method filter list. Any accessor method (getter) matching this, will not be used and instead direct field level access will be used to obtain the value from the field (reflection, etc.) when writing JSON.
WriteOptionsBuilderremoveMethodFilter(String filterName, MethodFilter filter)
- Remove a named
MethodFilterto the field filter chain.
Method Accessor
In json-io, "Method Accessors" are used to define how properties are accessed during serialization, typically
through getter methods. By default, json-io recognizes standard "get" and "is" prefixes for method accessors.
However, you can extend this functionality by defining custom accessor patterns and adding them to the method accessor chain.
Customizing Accessors:
- Flexibility: You can create custom patterns for method accessors to accommodate different naming conventions or method structures within your classes.
- Method Accessor Chain: Once defined, your custom accessors are added to a chain. During serialization,
json-ioconsults this chain to determine the correct methods to use for accessing field values.
Example Usage:
Suppose you have methods in your classes that use a prefix other than "get" or "is" for getters. You can define a custom accessor to handle these:
public class MyCustomAccessor implements MethodAccessor {
@Override
public boolean isAccessor(Method method) {
// Check if the method name starts with 'fetch' and it returns a value
return method.getName().startsWith("fetch") && method.getParameterTypes().length == 0;
}
@Override
public String getFieldNameFromAccessor(Method method) {
// Convert method name from 'fetchFieldName' to 'fieldName'
return method.getName().substring(5, 6).toLowerCase() + method.getName().substring(6);
}
}
// Add the custom accessor to json-io
new WriteOptionsBuilder().addMethodAccessor(new MyCustomAccessor());
This example shows how to set up a custom accessor for methods that start with "fetch," adapting json-io to use these methods as getters during serialization.
Benefits:
- Enhanced Compatibility: Allows json-io to handle a wider range of getter conventions, making it more adaptable to different coding styles.
- Customization: Enables precise control over which methods are used to access properties, ensuring that the serialized JSON matches specific requirements.
By leveraging custom method accessors, developers can significantly increase the adaptability and accuracy of JSON serialization in json-io, ensuring that it aligns perfectly with the application's data access patterns.
WriteOptionsBuilderaddAccessorFactory(AccessorFactory accessorFactory)
- Add a method accessor pattern to the method accessor chain.
Date Formatting for java.util.Date and java.sql.Date
In json-io, you can customize the serialization format for java.util.Date and java.sql.Date fields. By default,
these date types are serialized into JSON as numeric timestamps (long format), which are compact and efficient for
processing. However, this may not always be suitable depending on the application's requirements.
Configuring Date Formats:
- Default Numeric Format: The default serialization uses the numeric representation of the date (milliseconds since the Unix epoch), which is fast to serialize and deserialize but might not be human-readable.
- Custom String Formats: If a more readable format is required, you can specify a custom date format using any of
the standard JDK date formatting options. For example, you can use formats like
"yyyy-MM-dd'T'HH:mm:ss"to get an ISO 8601 compliant representation.
Example Usage:
To set a custom date format in json-io, you can use the following approach:
WriteOptions options = new WriteOptionsBuilder()
.dateFormat("yyyy-MM-dd'T'HH:mm:ss")
.build();
String json = JsonIo.toJson(yourDateObject, options);
This configuration will serialize date fields in the specified ISO date time format, making the JSON output more human-readable.
Convenience Methods:
- ISO Date Formats: json-io provides convenience methods to easily set short and long ISO date formats, simplifying the process of configuring common date representations.
Benefits:
- Flexibility: Allows the serialization format to be tailored to the needs of different applications, improving interoperability and readability.
- Standard Compliance: By using ISO formats or other standard date formats, the serialized JSON can be more easily consumed by various systems and services. Customizing date formats ensures that json-io outputs date information in a way that best fits the application's data handling and presentation requirements.
booleanisLongDateFormat()
- Returns
trueifjava.util.Dateandjava.sql.Dateare being written inlong(numeric) format.
WriteOptionsBuilderisoDateFormat()
- Changes the date-time format to the ISO date format: "yyyy-MM-ddThh:mm:ss.SSSZ". If millis are 0, the fractional portion is omitted.
WriteOptionsBuilderlongDateFormat()
- Changes the
java.util.Dateandjava.sql.Dateformat output to along,the number of seconds since Jan 1, 1970 at midnight. For speed, the default format islong.ReturnsWriteOptionsBuilderfor chained access.
Non-Referenceable Classes (Opposite of Instance Folding)
In json-io, small immutable classes are often treated as primitives, meaning there is no need to use @id/@ref
mechanisms typically required for object referencing. This approach is automatically applied to all primitives,
primitive wrappers, BigInteger, BigDecimal, Atomic*, java.util.Date, String, Class, and similar immutable
objects, which are marked as non-referenceable by default.
Customizing Non-Referenceable Classes:
- Adding Classes: You can extend this default behavior by marking additional immutable classes as non-referenceable, thereby treating them like primitives during serialization.
- Effect on Object Graph: This setting can alter the "shape" of your object graph. For example, if a
Stringvalue like "hello" appears multiple times in your data, each occurrence is normally treated as a separate instance. Without@id/@ref, each instance of "hello" is repeated in the JSON, enhancing readability but potentially increasing the size of the output.
Considerations:
- Instance Uniqueness: Utilizing
@idand@refcan help maintain instance uniqueness across the object graph. For example, if the string "hello" appears 25 times in your JSON, using@id= "n" and@ref= "n" for each occurrence can preserve the graph structure, ensuring that all references point to a single, shared instance. - Readability vs. Efficiency: Choosing not to use
@id/@reffor simple objects can make the JSON more readable and concise, as it avoids the overhead of tracking object identities. This is typically suitable for scenarios where object identity continuity is not critical.
Example Usage:
To mark additional classes as non-referenceable in json-io, you might configure your serialization settings like this:
new WriteOptionsBuilder().addNonReferenceable(MyImmutableClass.class);
This configuration treats instances of MyImmutableClass as primitives, not using @id/@ref for them, simplifying the JSON output while still maintaining a clear and accurate representation of the data.
Benefits:
- Simplicity: Treating simple, immutable objects as primitives simplifies the JSON output.
- Performance: Reduces the complexity of the serialization process by avoiding unnecessary references.
- Customization: Allows developers to tailor the serialization behavior to match the needs of their application, balancing between accuracy of data representation and simplicity of output.
By carefully selecting which classes are marked as non-referenceable, developers can optimize the serialization process in json-io to suit their specific requirements for data integrity and readability.
Annotation Alternative: Use @IoNonReferenceable on your class. See Annotations.
booleanisNonReferenceableClass(Class)
- Checks if a class is non-referenceable. Returns
trueif the passed in class is considered a non-referenceable class.
WriteOptionsBuilderaddNonReferenceableClass(Class)
- Adds a class to be considered "non-referenceable."
TOON Key Folding
When writing TOON format output using JsonIo.toToon(), you can enable key folding to produce more compact output. Key folding collapses single-key object chains into dotted notation.
How Key Folding Works
Without key folding (default):
data:
metadata:
value: 42
With key folding enabled:
data.metadata.value: 42
Key folding only applies when:
- Each nested object has exactly one key
- All key segments are valid identifiers (letters, digits, underscores, starting with letter/underscore)
Arrays are also supported in folded paths:
data.items[3]: foo,bar,baz
Configuration
booleanisToonKeyFolding()
- Returns
trueif TOON key folding is enabled for writing,falseotherwise. Default isfalse.
WriteOptionsBuildertoonKeyFolding(boolean enable)
- Enables or disables TOON key folding when writing TOON format. When
true, single-key object chains are collapsed into dotted notation.
Example Usage
// Without key folding (default)
String toon1 = JsonIo.toToon(data, null);
// Output:
// data:
// metadata:
// value: 42
// With key folding
WriteOptions options = new WriteOptionsBuilder()
.toonKeyFolding(true)
.build();
String toon2 = JsonIo.toToon(data, options);
// Output:
// data.metadata.value: 42
Reading Folded Keys
The TOON reader automatically handles both formats - folded dotted keys are expanded into nested structures during parsing. No special configuration is needed on the read side.
// This TOON input:
// config.database.host: localhost
// Parses to this structure:
// {config: {database: {host: "localhost"}}}
Quoted keys are preserved as literals (not expanded):
"dotted.key": value
// Parses to: {dotted.key: "value"}
Benefits
- Compact Output: Reduces output size by eliminating indentation for single-key chains
- Improved Readability: Dotted paths are easier to scan for deeply nested configuration values
- LLM Efficiency: Fewer tokens for the same data structure
- Round-trip Safe: Folded output parses back to identical data structures
TOON Delimiter
When writing TOON format output, you can configure the delimiter used in tabular arrays and inline primitive arrays. The default delimiter is a comma (,), but tab (\t) and pipe (|) are also supported.
Tab delimiters can further reduce token count since tabs tokenize more efficiently than commas in most BPE tokenizers, and they rarely appear in natural text (reducing the need for value quoting).
How TOON Delimiters Work
The delimiter affects three areas of TOON output:
- Tabular headers — field names in the
{field1,field2}header - Tabular rows — values in each data row
- Inline primitive arrays — compact single-line arrays like
1,2,3
The delimiter is encoded in the count bracket so the reader can auto-detect it:
- Comma (default):
[3]— no suffix needed - Tab:
[3\t]— tab character after the count - Pipe:
[3|]— pipe character after the count
Comma (default):
[3]{name,age}:
Alice,25
Bob,30
Charlie,22
Tab delimiter:
[3 ]{name age}:
Alice 25
Bob 30
Charlie 22
Pipe delimiter:
[3|]{name|age}:
Alice|25
Bob|30
Charlie|22
Configuration
chargetToonDelimiter()
- Returns the delimiter character used for TOON tabular arrays and inline primitive arrays. Supported values:
','(comma, default),'\t'(tab),'|'(pipe).
WriteOptionsBuildertoonDelimiter(char delimiter)
- Sets the delimiter for TOON tabular and inline array output. Must be
',','\t', or'|'. ThrowsIllegalArgumentExceptionfor unsupported delimiters.
Example Usage
// Default comma delimiter
String toon1 = JsonIo.toToon(employees, null);
// Output: [3]{name,age}: Alice,25 Bob,30 Charlie,22
// Tab delimiter
WriteOptions tabOpts = new WriteOptionsBuilder()
.toonDelimiter('\t')
.build();
String toon2 = JsonIo.toToon(employees, tabOpts);
// Output: [3\t]{name\tage}: Alice\t25 Bob\t30 Charlie\t22
// Pipe delimiter
WriteOptions pipeOpts = new WriteOptionsBuilder()
.toonDelimiter('|')
.build();
String toon3 = JsonIo.toToon(employees, pipeOpts);
// Output: [3|]{name|age}: Alice|25 Bob|30 Charlie|22
Quoting Behavior
When a value contains the active delimiter character, it is automatically quoted:
// With tab delimiter, commas in values do NOT need quoting
WriteOptions tabOpts = new WriteOptionsBuilder().toonDelimiter('\t').build();
// "Smith, John" is written as-is: Smith, John\t30
// With pipe delimiter, pipe in values IS quoted
WriteOptions pipeOpts = new WriteOptionsBuilder().toonDelimiter('|').build();
// "A|B" is written as: "A|B"|30
Reading Delimiter-Aware TOON
The TOON reader automatically detects the delimiter from the count bracket suffix ([N], [N\t], [N|]). No special read-side configuration is needed.
// Write with tab delimiter
WriteOptions opts = new WriteOptionsBuilder().toonDelimiter('\t').build();
String toon = JsonIo.toToon(data, opts);
// Read — delimiter auto-detected
List<?> restored = JsonIo.fromToon(toon, null).asClass(List.class);
Application Scoped Options (Full Lifecycle of JVM)
json-io allows the configuration of application-scoped options, which are settings that persist for the entire
lifecycle of the JVM. These settings ensure that all instances of WriteOptions are automatically configured with
the specified options from the startup of your application or service until its shutdown.
Understanding Application Scoped Options:
- Scope: These options are set at the application level and affect every
WriteOptionsinstance created during the JVM's lifecycle. This eliminates the need to repeatedly configure these settings for each instance. - Persistence: The settings are maintained in static memory throughout the JVM session, meaning they do not modify any files on disk but are retained across all operations during the session.
- Lifecycle: The term "JVM Lifecycle" refers to the period from when the application starts up to when it shuts down.
All changes to the application-scoped options during this period will affect any new instances of
WriteOptionscreated.
Benefits:
- Consistency: Ensures a consistent configuration across all serialization operations without manual reconfiguration for each instance.
- Efficiency: Reduces the overhead of repeatedly setting options for each new
WriteOptionsinstance, simplifying code and reducing the potential for configuration errors. - Control: Provides centralized control over the serialization settings, which is especially useful in large
applications where
WriteOptionsare frequently used.
Application-scoped options provide a powerful mechanism to manage serialization settings globally, enhancing uniformity and reducing the complexity of managing individual WriteOptions instances throughout an application's runtime.
addPermanentAlias
Call this method to add a permanent (JVM lifetime) alias of a class to a shorter, name. All WriteOptions
will automatically be created with permanent aliases added to them.
Annotation Alternative: Use @IoTypeName("Alias") on your class. See Annotations.
WriteOptionsBuilder.addPermanentAlias(
Class<?> clazz, String alias)
Remove Permanent Alias Type Names Matching
The removePermanentAliasTypeNamesMatching method in json-io provides a mechanism to permanently remove alias entries from the base WriteOptionsBuilder. This ensures that all new instances of WriteOptions created by WriteOptionsBuilder will not include these aliases for the lifetime of the JVM. This feature is particularly useful for dynamically adjusting the serialization behavior based on evolving application requirements.
Functionality:
- Alias Removal: This method removes substitution pairings, ensuring that written JSON will use the fully qualified class names instead of shorter, aliased names. It is effective for permanently un-aliasing classes that were previously aliased.
- Wildcard Pattern Matching: The API supports wildcard patterns containing
*,?, and regular characters, allowing for flexible specification of which class names should have their aliases removed.
Usage:
To remove aliases using patterns, you might use the method like this:
// Example of removing aliases that match a pattern
WriteOptionsBuilder.removePermanentAliasTypeNamesMatching("com.mycompany.*");
In this example, all aliases for classes within the com.mycompany package are removed from future WriteOptions instances.
Alternative Configuration:
Instead of programmatically removing aliases, you can manage aliases through a configuration file:
- Aliases File: You can place an aliases.txt file in the class path with your preferred aliases. json-io includes a comprehensive list of default aliases, but you can override these by providing your own file.
# Example content of aliases.txt
java.util.ArrayList=ArrayList
com.mycompany.MyClass=MyAlias
This file-based approach allows for static configuration of aliases, which might be easier to manage depending on your deployment and development processes.
Benefits:
- Flexibility and Control: Offers the ability to fine-tune which aliases are used in the serialization process, providing greater control over how data is represented in JSON.
- Adaptability: Facilitates the adaptation of serialization strategies without requiring code changes, especially useful in environments where classes or packages are dynamically loaded or updated.
By using the removePermanentAliasTypeNamesMatching() method, developers can ensure the 'write' side never gets ahead of
the 'read' side.
WriteOptionsBuilder.removePermanentAliasTypeNamesMatching(
String classNamePattern)
addPermanentNotExportedField
Call this method to add a permanent (JVM lifetime) excluded (not exported) field name of class. All WriteOptions will
automatically be created with the named field on the not-exported list.
Annotation Alternative: Use @IoIgnore on individual fields or @IoIgnoreProperties({"field1","field2"}) on the class for full both-sides exclusion. For write-only exclusion (field still deserialized from JSON, only output suppressed — useful for secrets like passwordHash) use @IoProperty(access = IoProperty.Access.WRITE_ONLY) on the field, or @IoIgnoreProperties(value = {...}, allowSetters = true) at the class level. Mirrors Jackson's @JsonProperty(access = WRITE_ONLY) / @JsonIgnoreProperties(allowSetters = true). See Annotations.
Method Aliases: addPermanentExcludedField is also exposed as addPermanentWriteOnlyField (Jackson-named — WRITE_ONLY from the Java-bean perspective: the bean's setter is invoked by the deserializer when JSON comes in, but its getter is not invoked by the serializer) and addPermanentDeserializeOnlyField (unambiguous JSON-direction name). The per-instance equivalents on the builder are addExcludedField, addWriteOnlyField, and addDeserializeOnlyField — all three delegate to the same internal map. Note: the per-instance variants share WriteOptionsBuilder's static field-accessor cache; for authoritative global exclusion call addPermanent* at application bootstrap.
WriteOptionsBuilder.addPermanentNotExportedField(
Class<?> clazz, String fieldName)WriteOptionsBuilder.addPermanentWriteOnlyField(
Class<?> clazz, String fieldName) — alias (Jackson-named)WriteOptionsBuilder.addPermanentDeserializeOnlyField(
Class<?> clazz, String fieldName) — alias (unambiguous)WriteOptionsBuilder.addExcludedField(
Class<?> clazz, String fieldName) — per-instance equivalentWriteOptionsBuilder.addWriteOnlyField(
Class<?> clazz, String fieldName) — per-instance alias (Jackson-named)WriteOptionsBuilder.addDeserializeOnlyField(
Class<?> clazz, String fieldName) — per-instance alias (unambiguous)
addPermanentNonRef
Call this method to add a permanent (JVM lifetime) class that should not be treated as referencable when being written out to JSON. This means it will never have an @id nor @ref. This feature is useful for small, immutable classes.
Annotation Alternative: Use @IoNonReferenceable on your class. See Annotations.
WriteOptionsBuilder.addPermanentNonRef(
Class<?> clazz)
addPermanentNotCustomWrittenClass
Register a class to be excluded from custom JSON serialization for the lifetime of the JVM.
This method prevents the specified class from being serialized by any custom writer, even if it inherits from a class that would normally use custom serialization. Once registered, this exclusion persists until the JVM terminates.
Annotation Alternative: Use @IoNotCustomWritten on your class. See Annotations.
WriteOptionsBuilder.addPermanentNotCustomWrittenClass(
Class<?> clazz)
addPermanentWriter
Call this method to add a permanent (JVM lifetime) custom JSON writer to json-io. It will associate the
clazz to the writer you pass in. The writers are found with isAssignableFrom(). If this is too broad, causing too
many classes to be associated to the custom writer, you can indicate that json-io should not use a custom write for a
particular class, by calling the addNotCustomWrittenClass() method.
Annotation Alternative: Use @IoCustomWriter(MyWriter.class) on your class. See Annotations.
WriteOptionsBuilder.addPermanentWriter(
Class<?> clazz, JsonClassWriter writer)
Add Permanent Non-Standard Getter
The addPermanentNonStandardGetter method in json-io allows you to define and add a permanent getter method for properties in Java objects where the method does not adhere to the standard getter naming conventions. This is particularly useful for ensuring compatibility and functionality across Java versions, especially given the restrictions in Java 17 and later on accessing private member variables reflectively.
Purpose:
- Non-Standard Naming Conventions: Some classes, like
java.time.Instant, may use getter methods with unique names that do not follow the traditionalgetPropertyName()format. For instance,getEpochSecond()for accessing thesecondfield. - Permanent Accessors: By adding a non-standard getter, you ensure that
json-iocan reliably access these properties across the JVM's lifecycle, without needing to conform to standard naming conventions.
Example Usage:
To add a non-standard getter for the second field of java.time.Instant, which uses the getEpochSecond() method, you would configure json-io as follows:
// Example of setting a permanent non-standard getter
WriteOptionsBuilder.addPermanentNonStandardGetter(Instant.class, "second", "getEpochSecond");
In this setup, json-io will use getEpochSecond instead of the expected getSecond to serialize the second field of
an Instant object.
Preconfigured Accessors:
- JDK Classes: It's worth noting that many non-standard accessors for JDK classes are already configured by default
in
json-io,including the one forjava.time.Instant.This preconfiguration simplifies integration and reduces the need for additional setup in common use cases.
Benefits:
- Flexibility and Compatibility: This feature provides flexibility in handling classes with non-standard getter methods and ensures compatibility with Java's encapsulation policies post-Java 16.
- Streamlined Integration: By preconfiguring getters for common classes and allowing custom configurations,
json-iofacilitates streamlined integration and usage, even with complex object models.
The addPermanentNonStandardGetter() API enhances the robustness of JSON serialization in json-io, accommodating advanced use cases and modern Java functionalities.
Annotation Alternative: Use @IoGetter("fieldName") on your getter method. See Annotations.
WriteOptionsBuilder.addPermanentNonStandardGetter(
Class<?> clazz, String field, String methodName)
addPermanentFieldFilter
WriteOptionsBuilder.addPermanentFieldFilter(
String name, FieldFilter fieldFilter)
Add a FieldFilter that is JVM lifecycle scoped. All WriteOptions instance will contain this filter. A FieldFilter is used to filter (eliminate) a particular field from being serialized. This allows you to filter a field by a field characteristic, for example, you can eliminate a particular type of field that occurs on Enums. See EnumFieldFilter for an example.
addPermanentMethodFilter
Add a MethodFilter that is JVM lifecycle scoped. All WriteOptions instances will contain this filter. A MethodFilter
is used to filter (eliminate) a method accessor (getter) from being called. For example, a getFoo() method which one
might think returns the Foo member variable, but instead performs undesired extra work before the value is accessed.
In this case, tell json-io to eliminate the getFoo() accessor and then json-io will use techniques to attempt
reading the field directly.
The MethodFilter is passed the Class and the method name and if it returns 'true' for that pairing, the 'getter' method
will be not be used. This reading/accessing of fields happens when json-io is accessing the Java objects to create
JSON content.
The String name is a unique name you give the filter. It must be unique amongst the method filters. The MethodFilter
is a derived implementation to be added that filters methods a new, particular way.
WriteOptionsBuilder.addPermanentMethodFilter(
String name, MethodFilter methodFilter)
addPermanentMethodNameFilter
Works like the addPermanentMethodFilter() with one simple difference. Whereas one must sublass MethodFilter, with
this API you pass it the Class and method name to filter, and it will create a NamedMethodFilter for you. No need
to create a new MethodFilter subclass.
To call the API, pass a unique name that is unique across all MethodFilters, the Class on which the accessor (getter) resides, and the name of the method.
WriteOptionsBuilder.addPermanentNamedMethodFilter(
String name, Class<?> clazz, String methodName)
removePermanentMethodFilter
Remove a permanently registered MethodFilter by its name.
WriteOptionsBuilder.removePermanentMethodFilter(
String name)
addPermanentAccessorFactory
Add an AccessorFactory that is JVM lifecycle scoped. All WriteOptions instances will contain this AccessorFactory.
It is the job of an AccessorFactory to provide a possible method name for a particular field. json-io ships with
a GetMethodAccessorFactory and an IsMethodAccessFactory. These produce a possible method name for a given field.
When a field on a Java class is being accessed (read), and it cannot be obtained directly, then all AccessoryFactory
instances will be consulted until an API can be used to read the field.
WriteOptionsBuilder.addPermanentAccessorFactory(
String name, AccessorFactory factory)
removePermanentAccessorFactory
Remove a permanently registered AccessorFactory by its name.
WriteOptionsBuilder.removePermanentAccessorFactory(
String name)
Add Permanent Core WriteOptions Settings
The following methods allow you to set permanent (JVM lifetime) configuration settings that will be inherited by all new WriteOptions instances. These settings can still be overridden locally in specific instances, but provide global defaults for your application.
addPermanentClassLoader
Sets the permanent ClassLoader for all new WriteOptions instances. This ClassLoader will be used to resolve String class names during JSON serialization.
WriteOptionsBuilder.addPermanentClassLoader(
ClassLoader classLoader)
addPermanentShortMetaKeys
Sets the permanent short meta keys setting for all new WriteOptions instances. When enabled, meta keys like @id become @i, @ref becomes @r, etc.
WriteOptionsBuilder.addPermanentShortMetaKeys(
boolean shortMetaKeys)
addPermanentShowTypeInfo
Sets the permanent type information display mode for all new WriteOptions instances. You can choose from four methods:
- Always: Type information is always included in JSON output
- Never: Type information is never included in JSON output
- Minimal: Type information is included only when necessary (default)
- MinimalPlus: Extends minimal mode with additional optimizations for collections, maps, and convertible types
WriteOptionsBuilder.addPermanentShowTypeInfoAlways()
WriteOptionsBuilder.addPermanentShowTypeInfoNever()
WriteOptionsBuilder.addPermanentShowTypeInfoMinimal()
WriteOptionsBuilder.addPermanentShowTypeInfoMinimalPlus()
addPermanentPrettyPrint
Sets the permanent pretty print setting for all new WriteOptions instances. When enabled, JSON output includes vertical white-space and indentations for readability.
WriteOptionsBuilder.addPermanentPrettyPrint(
boolean prettyPrint)
addPermanentLruSize
Sets the permanent LRU cache size for all new WriteOptions instances. This cache stores Class-to-Field and Class-to-Accessor mappings to improve performance.
WriteOptionsBuilder.addPermanentLruSize(
int lruSize)
Example: Set a smaller cache for memory-constrained environments:
WriteOptionsBuilder.addPermanentLruSize(500);
WriteOptions options = new WriteOptionsBuilder().build(); // Uses 500 LRU size
addPermanentWriteLongsAsStrings
Sets the permanent write longs as strings setting for all new WriteOptions instances. When enabled, long values are written as strings to prevent precision loss in JavaScript.
WriteOptionsBuilder.addPermanentWriteLongsAsStrings(
boolean writeLongsAsStrings)
addPermanentSkipNullFields
Sets the permanent skip null fields setting for all new WriteOptions instances. When enabled, fields with null values are not included in JSON output.
WriteOptionsBuilder.addPermanentSkipNullFields(
boolean skipNullFields)
addPermanentForceMapOutputAsTwoArrays
Sets the permanent force map output as two arrays setting for all new WriteOptions instances. When enabled, all Maps are written as @keys and @values arrays regardless of key types. This overrides stringifyMapKeys.
WriteOptionsBuilder.addPermanentForceMapOutputAsTwoArrays(
boolean forceMapOutputAsTwoArrays)
addPermanentStringifyMapKeys
Sets the permanent stringify map keys setting for all new WriteOptions instances. When enabled, non-String map keys with bidirectional String conversions (Long, Integer, UUID, Enum, BigDecimal, Date, etc.) are written as stringified keys in standard JSON object format instead of @keys/@items. Default is false.
WriteOptionsBuilder.addPermanentStringifyMapKeys(
boolean stringifyMapKeys)
addPermanentWriteOptionalAsObject
Sets the permanent writeOptionalAsObject setting for all new WriteOptions instances. When enabled, Optional, OptionalInt, OptionalLong, and OptionalDouble values are written in the legacy json-io object form ({"present":X,"value":Y}) instead of Jackson-compatible primitive form (bare value or null). Default is false.
WriteOptionsBuilder.addPermanentWriteOptionalAsObject(
boolean writeOptionalAsObject)
addPermanentPreserveLeafContainerIdentity
Sets the permanent preserveLeafContainerIdentity setting for all new WriteOptions instances. When enabled, List<String>, Map<UUID, Date>, byte[], String[], and other non-referenceable-element containers are traced for shared identity (@id/@ref emitted when the same container is referenced by multiple fields). Default is false (new in 4.101.0) — each reference is serialized as an independent copy, matching Jackson's default identity semantics. POJO-holding containers (List<Foo>, Map<String, Foo>, Foo[]) always have identity traced regardless of this flag. Only affects cycleSupport=true writes.
WriteOptionsBuilder.addPermanentPreserveLeafContainerIdentity(
boolean preserveLeafContainerIdentity)
addPermanentAllowNanAndInfinity
Sets the permanent allow NaN and Infinity setting for all new WriteOptions instances. When enabled, Double and Float NaN and Infinity values are serialized as-is rather than converted to null.
WriteOptionsBuilder.addPermanentAllowNanAndInfinity(
boolean allowNanAndInfinity)
addPermanentEnumPublicFieldsOnly
Sets the permanent enum public fields only setting for all new WriteOptions instances. When enabled, only public fields are included when serializing enums as objects.
WriteOptionsBuilder.addPermanentEnumPublicFieldsOnly(
boolean enumPublicFieldsOnly)
addPermanentEnumSetWrittenOldWay
Sets the permanent enum set written old way setting for all new WriteOptions instances. When enabled, EnumSet instances are written with @enum instead of @type for backward compatibility.
WriteOptionsBuilder.addPermanentEnumSetWrittenOldWay(
boolean enumSetWrittenOldWay)
addPermanentCloseStream
Sets the permanent close stream setting for all new WriteOptions instances. When enabled, the OutputStream is automatically closed after JSON writing is complete.
WriteOptionsBuilder.addPermanentCloseStream(
boolean closeStream)
addPermanentCycleSupport
Sets the permanent cycle support setting for all new WriteOptions instances. When enabled (default), the writer performs a traceReferences() pre-pass to identify multi-referenced objects and emit @id/@ref. When disabled, the pre-pass is skipped for ~35-40% faster serialization of acyclic data, identity markers are not emitted, and true cycles throw with remediation guidance.
WriteOptionsBuilder.addPermanentCycleSupport(
boolean cycleSupport)
addPermanentToonDelimiter
Sets the permanent TOON delimiter for all new WriteOptions instances. Must be ',' (comma, default), '\t' (tab), or '|' (pipe). This sets the default delimiter used for TOON tabular arrays and inline primitive arrays.
WriteOptionsBuilder.addPermanentToonDelimiter(
char delimiter)
Combined Configuration Example
You can configure multiple permanent settings together for your application's global defaults:
// Set global defaults for your application
WriteOptionsBuilder.addPermanentPrettyPrint(true);
WriteOptionsBuilder.addPermanentSkipNullFields(true);
WriteOptionsBuilder.addPermanentWriteLongsAsStrings(true);
WriteOptionsBuilder.addPermanentLruSize(2000);
// All future WriteOptions instances will inherit these settings
WriteOptions options1 = new WriteOptionsBuilder().build();
WriteOptions options2 = new WriteOptionsBuilder()
.prettyPrint(false) // Override permanent setting locally
.build();
// options1 uses: prettyPrint=true, skipNullFields=true, writeLongsAsStrings=true, lruSize=2000
// options2 uses: prettyPrint=false, skipNullFields=true, writeLongsAsStrings=true, lruSize=2000
Important Notes:
- Permanent settings are applied at JVM startup and affect all subsequent
WriteOptionsinstances - Local instance settings always override permanent settings for that specific instance
- Permanent settings are thread-safe and can be set from any thread
- Changes to permanent settings do not affect already-created
WriteOptionsinstances