Skip to content

Latest commit

 

History

History
894 lines (602 loc) · 64.6 KB

File metadata and controls

894 lines (602 loc) · 64.6 KB

Controlling the input JSON using ReadOptions

To configure read options for use with JsonIo.toJava(), you need to set up a ReadOptions instance using the ReadOptionsBuilder. Here's how you can create and customize a new ReadOptions instance:

ReadOptions readOptions = new ReadOptionsBuilder()
.feature1()
.feature2(args)
.build();
JsonIo.toJava(json, readOptions).asClass(Employee.class);

// Use a TypeHolder to specify the return type
JsonIo.toJava(json, readOptions).asType(new TypeHolder<String, List<Long>>(){});

Detailed Steps:

Initialization: Start by creating a ReadOptionsBuilder instance.

Feature Toggle: Enable or disable features by calling the corresponding methods on the builder. Each method corresponds to a specific feature you might want to enable (like .feature1()) or configure (like .feature2(args)).

Finalization: Call the .build() method to finalize and create a read-only ReadOptions instance. Once built, ReadOptions are immutable and can be safely reused across multiple read operations.

Additional Tips:

Documentation: For more detailed information on the available features and methods, refer to the Javadoc documentation for ReadOptionsBuilder. for ReadOptionsBuilder.

Multiple Instances: You can create multiple ReadOptions instances for different scenarios. Each instance is stateless once constructed.

Copying Instances: If you need to create a new ReadOptions instance based on an existing one, use new ReadOptionsBuilder(readOptionsToCopyFrom) to start with the copied settings.

By following these guidelines, you can effectively manage how JSON data is processed and ensure that your application handles data consistently across various contexts.

Constructors

Create new instances ofReadOptions.

new ReadOptionsBuilder().feature1().feature2(args).build()

  • Start with default options and add in feature1 and feature2(requires argument) and make read-only.

new ReadOptionsBuilder(ReadOptions other)

  • Copy all settings from the passed in 'other'ReadOptions.

Class Loader

TheClassLoaderin theReadOptonsis used by json-io to turn String class names intoClassinstances.

ClassLoader getClassLoader()

  • Returns the ClassLoader to resolve String class names into Class instances.

ReadOptionsBuilderclassLoader(ClassLoader loader)

  • Sets the ClassLoader to resolve String class names when reading JSON.

Handling Unknown Types

When parsing JSON, you might encounter class names that do not exist in your JVM. This feature allows you to control the behavior in such cases.

Default Behavior: By default, if the parser encounters an unknown class name, it will fail, and a JsonIoException is thrown. This ensures that all types used in your JSON must be known and available.

Handling with JsonObject: If you prefer not to have the parsing fail on unknown types, you can disable this default behavior. Instead, the parser will create a JsonObject (which implements Map) to store the content of the JSON object. This approach is beneficial because a JsonObject can dynamically store any field encountered in the JSON, regardless of the class structure, and provides additional metadata about the JSON structure.

Custom Class Handling: Alternatively, you can specify your own class to be used instead of the default JsonObject for storing the JSON data. This custom class should ideally be a type of Map to ensure that it can handle any arbitrary set of JSON keys and values. A common choice is LinkedHashMap to maintain insertion order. Using maps for unknown types provides flexibility, allowing JSON data to be parsed into a usable format even when the exact class structure is not predefined in the JVM.

boolean isFailOnUnknownType()

  • Returnstrue if an 'unknownTypeClass' is set,falseif it is not set. The default setting is true.

Class getUnknownTypeClass()

  • Get the Class used to store content when the indicated class cannot be loaded by the JVM. Defaults tonull. Whennull, JsonObject is used (which implements Map). It is usually best to use a Map derivative, as it will be able to have the various field names encountered to be 'set' into it.

ReadOptionsBuilder failOnUnknownType(boolean fail)

  • Set totrueto indicate that an exception should be thrown if an unknown class type is encountered,falseotherwise. Iffailisfalse,then the default class used will beJsonObject for the particular JSON object encountered. You can set this to your own class using theunknownTypeClass()feature (e.g., LinkedHashMap.class). The default setting is true.

Note: When using JsonIo.toMaps(), this option is automatically set to false since Map mode is designed to work without requiring classes on the classpath. You can override this by explicitly setting failOnUnknownType(true) if needed.

ReadOptionsBuilder unknownTypeClass(Class)

  • Set the class to use (defaults toJsonObject when null) when a JSON object is encountered and there is no corresponding Java class to instantiate to receive the values. Common alternatives include LinkedHashMap.class or HashMap.class.

Example: Parse any JSON into a Map graph

// Recommended: Use toMaps() API (automatically handles unknown types)
Map<String, Object> graph = JsonIo.toMaps(json).asClass(Map.class);

// Alternative: Manual configuration with toJava()
ReadOptions opts = new ReadOptionsBuilder()
        .returnAsJsonObjects()
        .failOnUnknownType(false)
        .build();
Map<String, Object> graph = JsonIo.toJava(json, opts).asClass(Map.class);

The toMaps() API automatically prevents errors for unrecognized @type values and ensures all objects are stored as maps.

MaxDepth - Security protection

Set the maximum object nesting level so that noStackOverflowExceptionshappen. Instead, you will get aJsonIoExceptionletting you know that the maximum object nesting level has been reached.

int getMaxDepth()

  • Return the max nesting depth for JSON {...}

ReadOptionsBuilder maxDepth(int depth)

  • Set the max depth to allow for nested JSON {...}. Set this to prevent security risk fromStackOverflowattack vectors.

lruSize - LRU Size [Cache of fields and filters]

Set the maximum number of Class to Field mappings and Class to filter 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.

int lruSize()

  • Return the LRU size. Default is 1000.

ReadOptionsBuilder lruSize(int size)

  • Set the max LRU cache size

Security Limits - Advanced DoS Protection

json-io provides configurable security limits to protect against denial-of-service (DoS) attacks via malicious JSON. These limits prevent unbounded memory consumption and excessive processing by enforcing reasonable bounds on various internal collections and processing stacks. All limits default to Integer.MAX_VALUE (unlimited) for backward compatibility - you must explicitly set them to enable protection.

int getMaxUnresolvedReferences()

  • Return the maximum number of unresolved references allowed during JSON processing. Default is Integer.MAX_VALUE (unlimited).

ReadOptionsBuilder maxUnresolvedReferences(int limit)

  • Set the maximum number of unresolved references allowed. Prevents memory exhaustion from DoS attacks via unbounded forward references. Recommended production value: 1000000.

int getMaxStackDepth()

  • Return the maximum traversal stack depth allowed during JSON processing. Default is Integer.MAX_VALUE (unlimited).

ReadOptionsBuilder maxStackDepth(int limit)

  • Set the maximum traversal stack depth allowed. Prevents stack overflow attacks via deeply nested structures. Recommended production value: 10000.

int getMaxMapsToRehash()

  • Return the maximum number of maps that can be queued for rehashing. Default is Integer.MAX_VALUE (unlimited).

ReadOptionsBuilder maxMapsToRehash(int limit)

  • Set the maximum number of maps that can be queued for rehashing. Prevents memory exhaustion from DoS attacks via excessive map creation. Recommended production value: 1000000.

int getMaxMissingFields()

  • Return the maximum number of missing fields that can be tracked. Default is Integer.MAX_VALUE (unlimited).

ReadOptionsBuilder maxMissingFields(int limit)

  • Set the maximum number of missing fields that can be tracked. Prevents memory exhaustion from DoS attacks via excessive missing field tracking. Recommended production value: 100000.

int getMaxObjectReferences()

  • Return the maximum number of object references that can be tracked during JSON processing. Default is 100,000,000.

ReadOptionsBuilder maxObjectReferences(int limit)

  • Set the maximum number of object references that can be tracked. Prevents memory exhaustion from DoS attacks via unbounded object reference growth. Recommended production value: 5000000.

int getMaxReferenceChainDepth()

  • Return the maximum depth of reference chains that can be traversed during JSON processing. Default is 100,000.

ReadOptionsBuilder maxReferenceChainDepth(int limit)

  • Set the maximum depth of reference chains that can be traversed. Prevents infinite loops from circular reference attacks. Recommended production value: 5000.

Example - Enabling Security Limits:

ReadOptions readOptions = new ReadOptionsBuilder()
    .maxUnresolvedReferences(1000000)  // 1M max unresolved references
    .maxStackDepth(10000)              // 10K max nesting depth
    .maxMapsToRehash(1000000)          // 1M max maps to rehash
    .maxMissingFields(100000)          // 100K max missing fields
    .maxObjectReferences(5000000)      // 5M max object references
    .maxReferenceChainDepth(5000)      // 5K max reference chain depth
    .build();

// These limits protect against malicious JSON while allowing normal usage
Object result = JsonIo.toJava(json, readOptions);

Permanent Security Limits (Application-Wide Configuration):

For production applications, you can set security limits permanently at JVM startup rather than configuring them for each ReadOptions instance. These permanent settings automatically apply to all new ReadOptions instances:

// Set at application initialization (typically in main() or static initializer)
ReadOptionsBuilder.addPermanentMaxUnresolvedReferences(1000000);  // 1M max unresolved references
ReadOptionsBuilder.addPermanentMaxStackDepth(10000);             // 10K max nesting depth
ReadOptionsBuilder.addPermanentMaxMapsToRehash(1000000);         // 1M max maps to rehash
ReadOptionsBuilder.addPermanentMaxMissingFields(100000);         // 100K max missing fields
ReadOptionsBuilder.addPermanentMaxObjectReferences(5000000);     // 5M max object references
ReadOptionsBuilder.addPermanentMaxReferenceChainDepth(5000);     // 5K max reference chain depth

// All subsequent ReadOptions instances will automatically inherit these limits
ReadOptions readOptions1 = new ReadOptionsBuilder().build();     // Has permanent limits
ReadOptions readOptions2 = new ReadOptionsBuilder()              // Can override if needed
    .maxStackDepth(5000)  // Override permanent setting for this instance
    .build();

When to Use Permanent vs. Local Settings:

  • Permanent Settings: Use for application-wide security policies that should apply consistently
  • Local Settings: Use for specific parsing contexts that need different limits
  • Recommendation: Set conservative permanent limits at startup, override locally when needed

Security Trade-offs:

  • Benefits: Protection against memory exhaustion and stack overflow attacks
  • Costs: Potential rejection of legitimately large JSON documents
  • Recommendation: Enable in production environments, tune limits based on your data patterns

Unsafe Mode - Object Instantiation

Control whether to use sun.misc.Unsafe for instantiating objects that cannot be created through normal constructors. This includes package-private classes, inner classes, and classes without public no-argument constructors.

boolean isUseUnsafe()

  • Return true if unsafe mode is enabled for object instantiation. Default is false for security.

ReadOptionsBuilder useUnsafe(boolean useUnsafe)

  • Enable or disable unsafe mode for object instantiation. When enabled, json-io can instantiate classes that have no public constructors, package-private classes, inner classes, and classes whose constructors throw exceptions. This bypasses normal Java constructor invocation and security mechanisms.

Important Security Considerations:

  • Default: Unsafe mode is false by default for security reasons
  • When to Enable: Only enable when you need to deserialize classes not designed for serialization AND you trust the JSON source
  • Risks: Bypasses Java's constructor validation and security checks
  • Benefits: Can deserialize any class regardless of constructor accessibility

Example - Enabling Unsafe Mode:

// Enable unsafe mode for deserializing problematic classes
ReadOptions readOptions = new ReadOptionsBuilder()
    .useUnsafe(true)  // Enable unsafe instantiation
    .build();

// Can now deserialize package-private, inner classes, etc.
Object result = JsonIo.toJava(json, readOptions);

Typical Use Cases for Unsafe Mode:

  • Deserializing third-party classes without public constructors
  • Working with package-private or inner classes
  • Handling classes whose constructors throw exceptions
  • Cloning complex object graphs with restricted constructors

Floating Point Options

Handling special floating point values and large numbers in JSON can be challenging due to limitations in standard formats and data types.

  • Nan and Infinity: Although the JSON specification does not natively support NaN, +Infinity, and -Infinity, json-io can be configured to recognize and correctly handle these values. This feature is disabled by default because not all external APIs and services support these non-standard JSON values. Enabling this feature allows such values to be correctly written to and read from JSON, ensuring compatibility within your application while possibly limiting interoperability with other systems.

  • Handling Large Numbers: JSON payloads can sometimes include floating point numbers that exceed the capacity of the standard Java Double as defined by IEEE 754. To address this, json-io offers several configuration options:

    • Always use BigDecimal: Treat all floating point numbers as BigDecimal, which can handle arbitrary precision.
    • Use BigDecimal when needed: Automatically determine whether to use BigDecimal or Double based on the size and precision of the number.
    • Never use BigDecimal: Stick to using Double for all floating point numbers, regardless of size.

These settings provide flexibility in how large or special floating point numbers are handled, allowing for both precision and performance optimization based on your specific application requirements.

booleanisAllowNanAndInfinity()

  • Returnstrueif set to allow serialization ofNaNandInfinityfordoublesandfloats.

ReadOptionsBuilder allowNanAndInfinity(boolean allow)

  • true will allowdoublesandfloatsto be output asNaNandINFINITY,falseand these values will come across asnull.

TOON Parsing Strictness

boolean isStrictToon()

  • Returns true when strict TOON parsing is enabled. Default is false.

ReadOptionsBuilder strictToon()

  • Enables strict TOON parsing mode.

ReadOptionsBuilder strictToon(boolean strictToon)

  • Enables or disables strict TOON parsing mode.

boolean isFloatingPointDouble()

  • return true if floating point values should always be returned as Double. This is the default.

ReadOptionsBuilder floatPointDouble()

  • Set Floating Point types to be returned as Double. This is the default.

boolean isFloatingPointBigDecimal()

  • return true if floating point values should always be returned as BigDecimal.

ReadOptionsBuilder floatPointBigDecimal()

  • Set Floating Point types to be returned as BigDecimal.

ReadOptionsBuilder floatPointBoth()

  • Set Floating Point types to be returned as either Double or BigDecimal, depending on precision required to hold the number sourced from JSON.

boolean isFloatingPointBoth()

  • return true if floating point values should always be returned dynamically, as Double or BigDecimal, favoring Double except when precision would be lost, then BigDecimal is returned.

Integer Options

Handling large integer values in JSON can be problematic, especially when they exceed the range that a standard Java Long can accommodate.

json-io provides several configuration options to manage how these large integers are handled during parsing:

  • Always use BigInteger: Configure json-io to treat all integer numbers as BigInteger, which supports integers of practically unlimited size.
  • Use BigInteger when needed: Allow json-io to decide dynamically whether to use BigInteger or Long based on the actual size of the integer present in the JSON. This can be particularly useful for optimizing performance without sacrificing the ability to handle large numbers when necessary.
  • Never use BigInteger: With this setting, all integers are handled as Long, which may lead to issues if the JSON contains integers outside the Long range.

These settings offer flexibility in dealing with large integers, ensuring that your application can handle both common cases efficiently and rare cases correctly.

boolean isIntegerTypeLong()

  • return true if integer values should always be returned as a Long. This is the default.

ReadOptionsBuilder integerTypeLong()

  • Set integer Types to be returned as Long. This is the default.

boolean isIntegerTypeBigInteger()

  • return true if integer values should always be returned as BigInteger.

ReadOptionsBuilder integerTypeBigInteger()

  • Set integer Types to be returned as BigInteger.

boolean isIntegerTypeBoth()

  • return true if integer values should always be returned dynamically, as Long or BigInteger, favoring Long except when precision would be lost, then BigInteger is returned.

ReadOptionsBuilder integerTypeBoth()

  • Set integer types to be returned as either Long or BigInteger, depending on precision required to hold the number sourced from JSON. If the value is within the range of Long.MIN_VALUE to Long.MAX_VALUE, then Long will be returned otherwise a BigInteger will be returned.

Close Stream

The handling of streams after JSON data is read can vary based on the specific needs of your application. json-io offers configurable options to either close the stream automatically after reading or keep it open for further use.

  • Automatic Stream Closing: By default, json-io can automatically close the InputStream once the JSON has been read. This is useful for single-use stream scenarios where no further data is expected.

  • Keep Stream Open: In cases where subsequent JSON data might be read from the same stream, such as with NDJSON (Newline Delimited JSON) formats that consist of multiple JSON objects separated by newlines ({...}\n{...}\n{...}), you can configure json-io to leave the InputStream open after each read. This allows for continuous reading without the need to reopen or recreate the stream.

For specific examples and implementation details, refer to the example at the beginning of the user guide.

boolean isCloseStream()

  • Returns true if set to automatically close stream after read (the default), or falseto leave stream open after reading from it.

ReadOptionsBuilder closeStream(boolean closeStream)

  • Sets the 'closeStream' setting,trueto turn on,falsewill turn off. The default setting isfalse.

Aliasing - Shorten Class Names in @type

Aliasing simplifies JSON output by converting fully qualified Java class names into shorter, simpler class names. For example, java.util.ArrayList can be aliased to just ArrayList, reducing the JSON content size and enhancing readability.

  • Default Aliases: By default, json-io includes aliases for many common JDK classes, which helps to minimize the JSON output size automatically.

  • Adding Custom Aliases: You can add custom aliases for your classes within the application. For instance, adding an alias from com.mycompany.Foo to Foo.

  • Scope of Aliases: The aliases added affect only the instance of ReadOptions created from a ReadOptionsBuilder. 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 externally by creating an aliases.txt file and placing it in the class path. json-io includes a comprehensive list of default aliases, 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.

Note: When adding an alias, json-io not only adds the alias for the class itself but also for its 1D, 2D, and 3D array representations, ensuring that all forms of the class are consistently aliased in the JSON output. Aliasing Foo also generates Foo[], Foo[][], and Foo[][][] aliases, ensuring consistency across all usages of the class in JSON

String getTypeNameAlias(String typeName)

  • Pass in a String class name, and it will return the alias for it, or it will return the same string you passed in (non-aliased).

Map<String, String> aliasTypeNames()

  • Returns Map<String, String> containing String class names to alias names. Use this API to see default aliases.

ReadOptionsBuilder aliasTypeNames(Map<String, String> aliasTypeNames)

  • Puts theMapcontaining String class names to alias names. The passed inMapwill be putAll() copied overwriting any entries that match values in the passed in Map. New entries in the Map are added.

ReadOptionsBuilder aliasTypeName(String typeName, String alias)

  • Sets the alias for a given class name.

ReadOptionsBuilder aliasTypeName(Class, String alias)

  • Sets the alias for a given class.

ReadOptionsBuilder removeAliasTypeNamesMatching(String typeNamePattern)

  • Remove alias entries from this ReadOptionsBuilder instance where the Java fully qualified string class name matches the passed in wildcard pattern. The typeNamePattern matches using a wild-card pattern, where * matches anything and ? matches one character. As many * or ? can be used as needed.

JSON Object Conversion Mode

json-io provides two modes for processing JSON data, controlled by the returnAsJsonObjects() and returnAsJavaObjects() methods:

  • returnAsJsonObjects(): When this mode is enabled, parsed JSON is returned as a graph of Maps and primitive types (JsonObjects), not as fully instantiated Java objects. This is useful when:

    • You want to examine or modify the JSON structure before final deserialization
    • The classes referenced in the JSON are not available in your JVM
    • You need to perform operations on the raw JSON data structure
  • returnAsJavaObjects(): This is the default mode, where JSON is fully converted to Java objects of the specified target types. This provides a direct mapping between JSON and your domain model.

Example with JsonObjects mode:

ReadOptions options = new ReadOptionsBuilder().returnAsJsonObjects().build();
Map<String, Object> jsonMap = JsonIo.toJava(json, options).asClass(Map.class);
// Modify the map if needed
jsonMap.put("name", "Updated Name");
// Later convert to Java objects if desired
Person person = JsonIo.toJava(jsonMap, new ReadOptionsBuilder().build()).asClass(Person.class);

boolean isReturningJsonObjects()

  • Returns true if parser is configured to return Map of Maps (JsonObjects) instead of instantiated Java objects.

ReadOptionsBuilder returnAsJsonObjects()

  • Configure parser to return Map of Maps (JsonObjects) instead of instantiated Java objects.

boolean isReturningJavaObjects()

  • Returns true if parser is configured to return fully instantiated Java objects (default behavior).

ReadOptionsBuilder returnAsJavaObjects()

  • Configure parser to return fully instantiated Java objects (default behavior).

Class Coercion

Class coercion is a powerful feature in json-io that allows you to transform specific Java classes into alternative implementations during the JSON parsing process. This can be particularly useful for converting derivative classes into more generic or usable forms when they are loaded into memory.

  • Example Conversion: For instance, json-io can convert java.util.Collections$UnmodifiableRandomAccessList into a more flexible ArrayList. This allows for mutable operations post-deserialization that are not possible with the original unmodifiable class.

  • Handling Collection Variants: The library already includes substitutions for many common JDK collection variants, such as singletons, synchronized collections, and various unmodifiable collections. These pre-configured substitutions ensure that collection types are coerced into their most usable forms without additional configuration.

  • Custom Coercions: You can extend this list by adding custom class coercions as needed. This feature enables you to specify alternative classes for specific types that might not be as straightforward or as flexible as required by your application's logic.

By leveraging class coercion, you can ensure that the objects resulting from JSON deserialization fit seamlessly into the operational context of your application, enhancing both functionality and developer convenience.

boolean isClassCoerced(String className)

  • Returntrueif the passed in Class name is being coerced to another type, falseotherwise.

Class getCoercedClass(Class)

  • Fetch the coerced class for the passed in fully qualified class name.

ReadOptionsBuilder coerceClass(String sourceClassName, Class destClass)

  • Coerce a class from one type (named in the JSON) to another type. For example, converting java.util.Collections$SingletonSetto aLinkedHashSet.class.

Missing Field Handler

The Missing Field Handler feature in json-io addresses scenarios where JSON contains fields that do not exist on the target Java object. This feature helps manage how such discrepancies are handled during the deserialization process.

  • Functionality: If a field in the JSON data does not correspond to any field on the Java object, rather than ignoring it or causing an error, json-io allows you to set up a handler that is specifically invoked for these missing fields.

  • Notification: The designated handler, configured through the MissingFieldHandler interface, is notified of all missing fields once deserialization completes. This allows for custom logic to be executed in response, such as logging missing information or taking corrective measures.

  • Further Details: For more comprehensive information on implementing and utilizing the MissingFieldHandler, refer to its Javadoc documentation.

This feature is particularly useful in maintaining robustness and flexibility in your application's data handling strategy, ensuring that unexpected data does not lead to unhandled exceptions or data integrity issues.

MissingFieldHandler getMissingFieldHandler()

  • Fetch the method that will be called when a field in the JSON is read in, yet there is no corresponding field on the destination object to receive the value.

ReadOptionsBuilder missingFieldHandler(MissingFieldHandler missingFieldHandler)

  • Pass themissing field handlerto be called when a field in the JSON is read in, yet there is no corresponding field on the destination object to receive the value.

Class Factory

In cases where json-io encounters difficulties instantiating a class or reading its data correctly through default processes, you can utilize the ClassFactory feature for a tailored solution.

  • Purpose of Class Factory: A ClassFactory is designed to create instances of a specific class and populate them with data directly from the JSON being parsed. This approach is particularly useful for classes that have non-standard constructors or initialization patterns that json-io does not handle natively.

  • How It Works: When a JSON object is encountered that maps to a class associated with a ClassFactory, the factory is invoked with the JSON data. It is the responsibility of the ClassFactory to construct an instance of the class, ensuring that all necessary data from the JSON is correctly translated and loaded into the new object.

  • Advantages Over Custom Readers: Using a ClassFactory is generally preferred over a CustomReader for class instantiation because it integrates more seamlessly with the standard deserialization process, maintaining consistency and reducing the potential for errors or data mismatches.

This feature allows for high customization and flexibility in how classes are instantiated from JSON data, enabling json-io to be effectively used with a wider range of Java classes, including those with complex construction requirements.

Map<Class, ClassFactory> getClassFactories()

  • FetchMapof all Class's associated toClassFactory's.

ClassFactory getClassFactory(Class)

  • Get theClassFactoryassociated to the passed in class.

ReadOptionsBuilder setClassFactories(Map<Class, ? extends ClassFactory> factories)

  • Associate multiple ClassFactory instances to Classes that needs help being constructed and read in. The Mapof entries associateClasstoClassFactory. TheClassFactoryclass knows how to instantiate and load the class associated to it.

ReadOptionsBuilder addClassFactory(Class clazz, ClassFactory factory)

  • Associate aClassFactoryto aClassthat needs help being constructed and read in. If you use this method more than once for the same class, the lastClassFactoryassociated to the class will overwrite any prior associations.

Custom Readers

Custom Readers in json-io offer a specialized mechanism for handling JSON deserialization when the default process does not meet specific needs. They allow for detailed customization of the JSON-to-object conversion process. This approach will likely be deprecated in a future release as using a ClassFactory is a superset capability (create AND load).

  • Purpose of Custom Readers: Custom Readers are designed to manage the deserialization of JSON content for specific classes, particularly when standard methods fail or require customization. They can also be applied to derived classes, ensuring consistent handling across a class hierarchy.

  • Recommendation to Use Class Factory First: Before implementing a Custom Reader, it's advisable to consider using a ClassFactory. This approach typically offers more straightforward integration with json-io's deserialization process, allowing for both instance creation and data loading simultaneously, thereby reducing complexity and potential issues.

  • Handling Unwanted Associations: Occasionally, a Custom Reader might inadvertently apply to classes for which it was not intended. To prevent this, you can explicitly exclude certain classes from being processed by a Custom Reader by adding them to the Not-Custom-Reader list. This ensures that only the intended classes are affected by the custom deserialization logic.

Custom Readers provide a powerful tool for extending and customizing the behavior of json-io, making it possible to tailor the deserialization process to fit exact requirements, particularly for complex or non-standard class structures.

Annotation Alternative: Use @IoCustomReader(MyReader.class) on your class to associate a custom reader without programmatic configuration. See Annotations.

Map<Class, JsonClassReader> getCustomReaderClasses()

  • FetchMapofClassto customJsonClassReader's used to read JSON when the class is encountered during serialization to JSON. This is the entireMapof Custom Readers.

boolean isCustomReaderClass(Class)

  • Returntrueif there is a custom reader class associated to the passed in class, false otherwise.

ReadOptionsBuilder setCustomReaderClasses(Map<? extends Class>, ? extends JsonClassReader> customReaderClasses)

  • Set all custom readers at once. Pass in aMapofClasstoJsonClassReader. Set the passed inMapas the establishedMapof custom readers to be used when reading JSON. Using this method more than once, will set the custom readers to only the values from theMapin the last call made.

ReadOptionsBuilder addCustomReaderClass(Class, JsonClassReader customReader)

  • Add a single association of a class to a custom reader. If you add another associated reader to the same class, the last one added will overwrite the prior association.

Not Custom Reader

In json-io, the Not Custom Reader list serves as a mechanism to exclude specific classes from being processed by a Custom Reader. This feature is particularly useful in managing class inheritance issues where a Custom Reader might unintentionally apply to subclasses.

  • Purpose: This list allows you to specify classes that should not be associated with any Custom Reader. By adding a class to this list, you ensure that it bypasses the custom deserialization process, even if a Custom Reader exists that could potentially apply due to class inheritance.

  • Use Case: It's often used to maintain the default deserialization behavior for certain classes within a hierarchy that otherwise might inherit a Custom Reader's behavior, which could lead to incorrect or unwanted data handling.

  • Implementation: Simply add the classes you want to exclude to the Not Custom Reader list. This straightforward approach helps maintain clarity and control over which classes are affected by custom deserialization logic, ensuring that only the intended classes are modified.

This functionality is essential for maintaining precise control over the deserialization process in complex inheritance scenarios, preventing unwanted side effects from broadly applied Custom Readers.

Annotation Alternative: Use @IoNotCustomReader on your class to suppress custom reader inheritance. See Annotations.

boolean isNotCustomReaderClass(Class)

  • Pass in Class to check if it's on the not-customized list. Classes are added to this list when a class is being picked up through inheritance, and you don't want it to have a custom reader associated to it.

Set<Class<?>> getNotCustomReadClasses()

  • Fetch theSetof all Classes on the not-customized list.

ReadOptionsBuilder addNotCustomReaderClass(Class notCustomClass)

  • Add a class to the not-customized list - the list of classes that you do not want to be picked up by a custom reader (that could happen through inheritance).

ReadOptionsBuilder setNotCustomReaderClasses(Collection<Class> notCustomClasses)

  • Initialize the list of classes on the non-customized list. All prior associations will be dropped and this Collection will establish the new list.

Non-Referenceable Classes (Opposite of Instance Folding)

In json-io, certain classes are treated as non-referenceable, meaning they do not use @id/@ref tags in JSON, as they are considered equivalent to primitives due to their immutable nature.

  • Default Non-Referenceable Classes: By default, all primitive types, primitive wrappers, BigInteger, BigDecimal, Atomic* types, java.util.Date, String, Class, and ZonedDateTime are treated as non-referenceable. This setup ensures that instances of these types are always serialized directly into the JSON without references.

  • Customizing Non-Referenceable Classes: You have the option to designate additional immutable classes as non-referenceable. This can be useful for custom classes that are immutable and do not require identity preservation across different fields in the JSON object.

  • Implications of Non-Referenceability: One side effect of marking classes as non-referenceable is the alteration of the "shape" of your object graph. For example, multiple fields pointing to the same String, BigDecimal, or Integer instance will not be recognized as the same instance (==) in the deserialized object. Each appearance is treated as a separate instance.

  • Handling Common Values: json-io automatically collapses common logical primitive values to maintain consistency. For instance, the string representations of "true", "false", the numbers -1, 0, and 1, and the strings "0" through "9", "on", "off", and other common strings are treated such that multiple occurrences are represented by a single instance in memory, promoting efficiency and reducing memory usage.

This feature enhances the efficiency of serialization and deserialization processes, especially for common and frequently used immutable values, while allowing for customization to suit specific application needs.

Annotation Alternative: Use @IoNonReferenceable on your class to mark it non-referenceable without programmatic configuration. See Annotations.

boolean isNonReferenceableClass(Class)

  • Checks if a class is non-referenceable. Returnstrueif the passed in class is considered a non-referenceable class.

Collection<Class> getNonReferenceableClasses()

  • Returns aCollectionof all non-referenceable classes.

ReadOptionsBuilder addNonReferenceableClass(Class)

  • Adds a class to be considered "non-referenceable." Examples are the built-in primitives.

Add InjectorFactory

The addInjectorFactory method in json-io allows you to specify a custom, global approach for injecting values into an object. For instance, if you have a specific way of declaring setter methods and want them to be invoked when setting values, this method can accommodate that. Additionally, it can be used to customize value injection for inner classes with special naming conventions.

ReadOptionsBuilder.addInjectorFactory(InjectorFactory factory)

Add FieldFilter

The addFieldFilter method in json-io allows you to add a custom filter to eliminate fields from consideration. There are already two built-in FieldFilters, one for filtering out static fields and another for filtering out Enum fields.

ReadOptionsBuilder.addFieldFilter(FieldFilter filter)


Application Scoped Options (Full Lifecycle of JVM)

json-io provides a set of application-scoped options that are effective for the entire lifecycle of the JVM, from start-up to shutdown. These settings allow you to define certain behaviors or configurations globally, so they are automatically applied to all instances of ReadOptions created throughout the application.

  • Scope of Application Settings: Once set, these options persist across the JVM lifecycle, ensuring that every ReadOptions instance inherits the same configurations without needing individual setup. This simplifies the management of read options across various parts of your application.

  • Impact on the JVM: These settings do not alter any files on disk or engage in any operations outside of the JVM's memory. Instead, they modify static memory allocations that influence the behavior of ReadOptions instances created during the JVM's operation.

  • Advantages: By using these permanent settings, you ensure a consistent configuration that is maintained across all operations, reducing redundancy and potential for configuration errors in individual instances.

This feature is particularly useful for large applications or services where maintaining consistent behavior across numerous operations is crucial. It also simplifies setup and maintenance by centralizing configuration changes to a single point in the application lifecycle.

Add Permanent Class Factory

The addPermanentClassFactory method in json-io allows you to establish a permanent solution for class instantiation that is reused across the application. This is particularly beneficial for classes that cannot be instantiated through standard constructors due to access restrictions or specialized construction requirements.

  • Purpose: Use this method to associate a ClassFactory that you implement with a specific class. This factory will handle the instantiation of instances of the designated class throughout the application's lifecycle.

  • Implementation: Implement the ClassFactory interface and define how instances of the target class should be created from the JSON object (represented as a Map or JsonObject). The factory is responsible for creating the class instance and populating it with values from the JSON data.

  • Usage Scenario: This method is invaluable when dealing with classes that have private constructors or require complex setup that the default deserialization process cannot handle. By setting up a ClassFactory, you ensure that json-io can effectively manage these classes whenever they are encountered in JSON data.

  • Application Scope: Once added, the class factory is applied universally to all subsequent ReadOptions instances created within the JVM, ensuring consistent behavior and reducing the need for repeated configuration.

This approach not only facilitates customization of the deserialization process for complex class structures but also enhances the robustness and flexibility of your application by ensuring that all components handle specific class constructions consistently.

ReadOptionsBuilder.addPermanentClassFactory(Class<?> sourceClass, ClassFactory factory)

Add Permanent Coerced Type

The addPermanentCoercedType method in json-io enables you to define a permanent coercion from one class to another that persists for the lifetime of the JVM. This feature is particularly useful for handling special cases where certain classes, like proxies or wrappers, need to be treated as more standard or manageable types during deserialization.

  • Purpose: Use this method to permanently map one class type to another within the deserialization process. This is essential for scenarios where default handling of certain classes does not meet the specific needs of your application.

  • Typical Use Cases: Common examples include converting proxy classes, such as those created by frameworks like Hibernate (e.g., HibernateBags), into regular JVM collections. This allows for more straightforward handling and integration within your application, avoiding complications that might arise from using specialized or proxied objects.

  • Implementation and Effect: Once set, this coercion will automatically apply whenever the specified class type is encountered in JSON data, converting it to the desired type. The mapping is added globally and affects all instances of ReadOptions created throughout the JVM's operation.

  • Advantages: By establishing a permanent type coercion, you ensure consistent behavior across your application, simplifying the handling of specific class types and enhancing the predictability and reliability of your data processing.

This method is a powerful tool for customizing how classes are deserialized, ensuring that they fit into the operational and data models of your application seamlessly.

ReadOptionsBuilder.addPermanentCoercedType(Class<?> sourceClass, Class<?> destinationClass)

Add Permanent Alias

The addPermanentAlias method in json-io allows you to establish a permanent alias for a class throughout the lifetime of the JVM. This method is used to map a fully qualified class name to a shorter alias that is easier to manage and more readable in JSON content.

  • Purpose: This method simplifies JSON serialization and deserialization by substituting long class names with shorter, more manageable aliases. These aliases are used in the @type field within the JSON, facilitating a cleaner and more concise representation of object types.

  • Implementation Details: By default, the @type field is added to JSON only when the class type cannot be automatically inferred from the context, such as the root class, field type, array component type, or template argument type. Using this method, you can ensure that a specific class is always represented by its alias, regardless of context.

  • Usage Scenario: This is particularly useful for reducing JSON size and improving readability when dealing with complex Java class names that are frequently used across your application's JSON communications.

  • Long-term Consistency: Adding a permanent alias ensures that the aliasing is consistent across all JSON operations within the application from startup to shutdown, without the need to redefine or reassess aliasing in each individual operation or session.

This feature enhances the manageability and clarity of JSON output and input within large applications, especially those that utilize complex object hierarchies or extensive data interchange.

Annotation Alternative: Use @IoTypeName("Alias") on your class. See Annotations.

ReadOptionsBuilder.addPermanentAlias(Class<?> sourceClass, String alias)

Remove Permanent Alias Type Names Matching

The removePermanentAliasTypeNamesMatching method in json-io is designed to permanently remove aliases based on specified wildcard patterns from the "base" ReadOptionsBuilder. This ensures that any new instances of ReadOptions created from ReadOptionsBuilders will not include these aliases for the duration of the JVM's lifetime.

  • Purpose: This method is used to delete alias entries that you no longer wish to use, affecting how JSON is read and recognized within your application.

  • Caution on Usage: While it's generally safe to maintain a broad set of aliases within ReadOptions, be cautious when removing aliases, especially on the 'read' side, to avoid disrupting data ingestion. Conversely, managing WriteOptions should be done more stringently, especially in microservice environments where consistent alias interpretation across services is critical.

  • Operational Implications: This action modifies the alias configuration globally within the JVM, ensuring that all future ReadOptions instances conform to the new alias settings. It's recommended to expand your READ alias support before enhancing WRITE alias support unless you are updating all related services simultaneously to accommodate new aliases.

  • Wildcard Pattern Matching: The API accepts wildcard patterns (*, ?, and regular characters) to identify which aliases to remove, allowing for flexible and powerful pattern matching against fully qualified class names stored in its cache.

  • Alternative Method: As an alternative to using this API programmatically, you can manage aliases by placing a custom aliases.txt file in the class path. json-io provides a comprehensive default list, but you can override this with your own configurations if preferred.

This method provides a robust tool for managing how aliases are used in JSON serialization and deserialization, ensuring that alias configurations are kept up-to-date and relevant to the application's current operational needs.

WriteOptionsBuilder.removePermanentAliasTypeNamesMatching(String classNamePattern)

Add Permanent Reader

The addPermanentReader method in json-io allows you to permanently associate a custom JSON reader with a specific class within the JVM's lifecycle. This ensures that the custom reader is used whenever instances of the class are encountered during JSON deserialization. We recommend using a ClassFactory over a CustomReader, as it allows both instantiation AND loading.

  • Purpose: This method is used to customize how JSON data is parsed into Java objects, particularly for classes where the default deserialization does not suffice or needs special handling.

  • Implementation Details: When you add a custom reader, it is associated with the class 'c' and any subclasses thereof, as determined by Class.isAssignableFrom(). This method provides a robust way to ensure that your custom parsing logic is applied consistently throughout your application.

  • Managing Broad Associations: If the association of the custom reader becomes too broad—potentially affecting more classes than intended—you can fine-tune which classes should not use the custom reader. This is achieved by using the ReadOptionsBuilder.addNotCustomReader() method, which excludes specified classes from being processed by the custom reader.

  • Permanent Configuration: Once added, the custom reader remains active and associated with the class for the entire duration of the JVM's operation (statically set). This permanence ensures that the custom behavior is reliably reproduced across all operations within the application without the need for repeated configuration.

  • Usage Considerations: This method is particularly useful in complex applications where certain classes require specialized deserialization that the default mechanisms cannot provide. It offers a powerful tool for developers to precisely control how data is interpreted and integrated into the application.

By employing addPermanentReader, developers can ensure that their specific deserialization requirements are met, enhancing the flexibility and robustness of data handling within their applications.

ReadOptionsBuilder.addPermanentReader(Class<?> c, JsonClassReader reader)

Add Permanent Non-Referenceable Class

The addPermanentNonReferenceableClass method in json-io allows you to designate specific classes as non-referenceable across the entire lifecycle of the JVM. This setting ensures that these classes are treated as simple values, similar to primitives, without using @id/@ref mechanisms in the serialized JSON.

  • Purpose: This method is used to optimize JSON serialization for classes that are immutable or effectively act like primitive values, where no references are needed because they do not benefit from the @id/@ref mechanism.

  • Impact: By marking a class as non-referenceable, you ensure that instances of this class in JSON are always serialized directly rather than as references. This can greatly simplify the JSON output and prevent unnecessary complexity in the data structure.

  • Application Scope: Once a class is added to the non-referenceable list, this configuration is applied globally across all ReadOptions instances created during the JVM's runtime. This means there is no need to repeatedly specify this setting for each new instance, enhancing consistency and reducing setup redundancy.

  • Use Cases: Typically used for small, immutable objects that are frequently instantiated with the same values, such as Color objects, custom Money or Quantity classes, which do not change once created and are used extensively throughout an application.

Adding classes to the non-referenceable list can significantly enhance the performance and clarity of JSON serialization processes, especially in complex systems where certain objects are better handled as value types rather than reference types.

Annotation Alternative: Use @IoNonReferenceable on your class. See Annotations.

ReadOptionsBuilder.addPermanentNonReferenceableClass(Class<?> clazz)

Add Permanent Not Custom Read Class

The addPermanentNotCustomReadClass method in json-io allows you to exempt specific classes from custom deserialization across the entire lifecycle of the JVM. This setting ensures these classes bypass any registered custom readers during JSON parsing.

  • Purpose: This method prevents a class from being processed by custom readers, even if it inherits from a class that would normally use custom deserialization. It essentially forces the class to use the default deserialization behavior.

  • Impact: By marking a class as not custom read, you ensure that instances of this class in JSON are always deserialized using the standard mechanisms, regardless of any custom readers that might apply through inheritance or direct registration.

  • Application Scope: Once a class is added to the not-custom-read list, this configuration is applied globally across all ReadOptions instances created during the JVM's runtime. This means there is no need to repeatedly specify this setting for each new instance, enhancing consistency and reducing setup redundancy.

  • Use Cases: Typically used when you need to override inheritance-based custom reader application, or when you want to ensure that certain classes are always deserialized using the default behavior regardless of other configuration settings.

Adding classes to the not-custom-read list can help control deserialization behavior in complex class hierarchies, particularly when you need exceptions to your custom reader rules.

Annotation Alternative: Use @IoNotCustomReader on your class. See Annotations.

ReadOptionsBuilder.addPermanentNotCustomReadClass(Class<?> clazz)

Add Permanent Not Imported Field

The addPermanentNotImportedField method in json-io allows you to permanently exclude specific fields from being deserialized into Java objects. This feature is crucial for managing how JSON data is processed into Java representations, particularly when certain JSON fields should not be transferred into the resulting Java object.

  • Purpose: Use this method to specify fields in the JSON that should be ignored during the deserialization process. This is especially useful for JSON data that contains fields irrelevant to the application logic or potentially problematic when mapping to Java classes.

  • Functionality: Fields added through this method will not be set (injected) into the Java objects, even if they exist in the incoming JSON. This exclusion is maintained throughout the JVM's lifetime, ensuring that all instances of ReadOptions created will automatically apply these settings.

  • Consistency Across Reads: By configuring fields to be ignored permanently, you maintain consistent behavior in how data is read across different parts of your application, without needing to repeatedly specify these exclusions in each new ReadOptions instance.

  • Comparison with Write Options: Similar to how addPermanentNotImportedField works for read operations, WriteOptionsBuilder provides analogous capabilities for write operations, allowing you to exclude fields from being included in the serialized JSON.

This method enhances data handling efficiency and accuracy within your application by ensuring that only relevant and necessary data is processed during JSON deserialization, simplifying object models and reducing potential errors.

Annotation Alternative: Use @IoIgnore on individual fields or @IoIgnoreProperties({"field1","field2"}) on the class. See Annotations.

ReadOptionsBuilder.addPermanentNotImportedField(Class<?> clazz, String fieldName)

Add Permanent Non-Standard Setter

The addPermanentNonStandardSetter method in json-io allows you to define custom setter methods for specific fields in a class when the standard JavaBean naming conventions do not apply. This is particularly useful for integrating custom or legacy code into your JSON serialization/deserialization processes.

  • Purpose: This method is used to associate non-standard setter methods with specific fields, ensuring that json-io can correctly apply data from JSON to Java objects even when setter methods are named unconventionally.

  • Functionality: When you specify a non-standard setter, json-io will invoke this method instead of the standard setter during the deserialization process. This is essential for classes where the setter methods do not follow the typical setFieldName format.

  • Example Usage: An example of using this feature is with the Throwable class in Java. Typically, to set a cause on a Throwable, the initCause() method is used instead of a standard setter. Configuring addPermanentNonStandardSetter(Throwable.class, "cause", "initCause") instructs json-io to use initCause() to set the cause from the JSON data:

Annotation Alternative: Use @IoSetter("fieldName") on your setter method. See Annotations.

ReadOptionsBuilder.addPermanentNonStandardSetter(Class<?> clazz, String fieldName, String setterName)

Add Permanent Core ReadOptions Settings

json-io provides permanent configuration methods for core ReadOptions settings that affect fundamental JSON parsing behavior. These settings are applied globally across the JVM lifecycle and automatically inherited by all new ReadOptions instances.

Add Permanent Max Depth

The addPermanentMaxDepth method allows you to set a permanent maximum parsing depth limit that protects against stack overflow attacks via deeply nested JSON structures.

  • Purpose: This method establishes a global limit on JSON nesting depth to prevent StackOverflowException from malicious or malformed JSON. Once set, all new ReadOptions instances will inherit this limit automatically.

  • Security Benefits: By setting a permanent max depth, you ensure consistent protection across your entire application without needing to configure each ReadOptions instance individually.

  • Validation: The method validates that the depth limit is at least 1, throwing a JsonIoException for invalid values.

  • Default Value: The default permanent max depth is 1000, which provides good protection while allowing reasonable nesting levels for normal use cases.

Example Usage:

// Set at application startup for global protection
ReadOptionsBuilder.addPermanentMaxDepth(500);  // Limit to 500 levels of nesting

// All subsequent ReadOptions instances inherit this limit
ReadOptions options1 = new ReadOptionsBuilder().build();     // Has 500 max depth
ReadOptions options2 = new ReadOptionsBuilder()              // Can override if needed
    .maxDepth(1000)  // Override permanent setting for this instance
    .build();

ReadOptionsBuilder.addPermanentMaxDepth(int maxDepth)

Add Permanent LRU Size

The addPermanentLruSize method allows you to set a permanent LRU cache size that controls memory usage for field and filter mappings across your application.

  • Purpose: This method establishes a global LRU cache size for Class to Field mappings and Class to filter mappings. The LRU cache automatically evicts infrequently used entries to control memory footprint.

  • Memory Management: By setting a permanent LRU size, you ensure consistent memory usage patterns across your application. Larger caches improve performance but use more memory, while smaller caches reduce memory usage but may require more frequent cache rebuilding.

  • Validation: The method validates that the LRU size is at least 1, throwing a JsonIoException for invalid values.

  • Default Value: The default permanent LRU size is 1000, which provides a good balance between performance and memory usage for most applications.

Example Usage:

// Set at application startup for consistent memory management
ReadOptionsBuilder.addPermanentLruSize(1000);  // Larger cache for better performance

// All subsequent ReadOptions instances inherit this cache size
ReadOptions options = new ReadOptionsBuilder().build();     // Has 1000 LRU size

ReadOptionsBuilder.addPermanentLruSize(int lruSize)

Add Permanent Allow NaN and Infinity

The addPermanentAllowNanAndInfinity method allows you to set a permanent policy for handling special floating point values (NaN, +Infinity, -Infinity) in JSON parsing.

  • Purpose: This method establishes a global policy for handling IEEE 754 special floating point values. When enabled, these values are preserved during JSON parsing; when disabled, they are converted to null.

  • Compatibility Considerations: While JSON specification does not natively support these values, enabling this feature allows compatibility with JSON produced by systems that include them. Disabling ensures strict JSON compliance.

  • Default Value: The default permanent setting is false (do not allow NaN and Infinity), ensuring strict JSON compliance by default.

Example Usage:

// Set at application startup for strict JSON compliance
ReadOptionsBuilder.addPermanentAllowNanAndInfinity(true);   // Allow NaN/Infinity values

// All subsequent ReadOptions instances inherit this policy
ReadOptions options = new ReadOptionsBuilder().build();     // Has strict floating point handling

ReadOptionsBuilder.addPermanentAllowNanAndInfinity(boolean allowNanAndInfinity)

Add Permanent Fail On Unknown Type

The addPermanentFailOnUnknownType method allows you to set a permanent policy for handling unknown class types encountered in JSON parsing.

  • Purpose: This method establishes a global policy for unknown type handling. When enabled, encountering an unknown @type in JSON causes a JsonIoException; when disabled, unknown types are converted to JsonObject instances (which implements Map).

  • Security Implications: Enabling strict unknown type handling ensures that all types in your JSON are explicitly known and trusted. Disabling provides flexibility for handling dynamic or external JSON with unknown types.

  • Default Value: The default permanent setting is true (fail on unknown types), ensuring strict type validation and security by default.

Example Usage:

// Set at application startup for strict type validation
ReadOptionsBuilder.addPermanentFailOnUnknownType(false); // Allow unknown types

// All subsequent ReadOptions instances inherit this policy
ReadOptions options = new ReadOptionsBuilder().build();     // Has strict type validation

ReadOptionsBuilder.addPermanentFailOnUnknownType(boolean failOnUnknownType)

Add Permanent Close Stream

The addPermanentCloseStream method allows you to set a permanent policy for input stream handling after JSON parsing completes.

  • Purpose: This method establishes a global policy for stream management. When enabled, input streams are automatically closed after parsing; when disabled, streams remain open for potential subsequent operations.

  • Resource Management: Enabling automatic stream closing ensures proper resource cleanup and prevents resource leaks. Disabling is useful for scenarios like NDJSON (Newline Delimited JSON) where multiple JSON objects are read from the same stream.

  • Default Value: The default permanent setting is true (close streams automatically), promoting good resource management practices while allowing applications to opt out when needed.

Example Usage:

// Set at application startup for NDJSON handling
ReadOptionsBuilder.addPermanentCloseStream(false);  // Keep streams open for multiple reads

// All subsequent ReadOptions instances inherit this policy
ReadOptions options = new ReadOptionsBuilder().build();     // Keeps streams open

ReadOptionsBuilder.addPermanentCloseStream(boolean closeStream)

Combined Configuration Example:

// Configure all permanent core settings at application startup
public class ApplicationConfig {
    static {
        // Security and performance settings
        ReadOptionsBuilder.addPermanentMaxDepth(500);                    // Limit nesting depth
        ReadOptionsBuilder.addPermanentLruSize(1000);                    // Optimize memory usage
        
        // JSON compatibility settings  
        ReadOptionsBuilder.addPermanentAllowNanAndInfinity(true);        // Allow NaN/Infinity values
        ReadOptionsBuilder.addPermanentFailOnUnknownType(false);         // Allow unknown types
        
        // Resource management settings
        ReadOptionsBuilder.addPermanentCloseStream(true);                // Automatic cleanup
    }
}

// All ReadOptions instances throughout the application automatically inherit these settings
ReadOptions defaultOptions = new ReadOptionsBuilder().build();

// Individual instances can still override permanent settings when needed
ReadOptions customOptions = new ReadOptionsBuilder()
    .maxDepth(1000)                    // Override permanent max depth
    .allowNanAndInfinity(false)        // Override permanent NaN/Infinity policy
    .build();