# Jackson Core - Streaming JSON Processing Library Jackson Core is the foundational streaming JSON processing library for the Jackson ecosystem. It provides low-level incremental ("streaming") parser and generator abstractions for reading and writing JSON content efficiently. The core abstractions (`JsonParser`, `JsonGenerator`, `TokenStreamFactory`) are format-agnostic despite JSON-centric naming, serving as the base for other data formats like Smile, CBOR, XML, YAML, CSV, and Protobuf. This package serves as the foundation for [Jackson Databind](https://github.com/FasterXML/jackson-databind) and requires JDK 17+ for Jackson 3.x. Factory instances are thread-safe and reusable after configuration, with most applications using a single globally shared factory instance for optimal performance through internal buffer recycling. --- ## JsonFactory - Creating Parsers and Generators `JsonFactory` is the main factory class for creating JSON parsers and generators. It's thread-safe, reusable, and handles internal buffer recycling for optimal performance. ```java import tools.jackson.core.json.JsonFactory; import tools.jackson.core.JsonParser; import tools.jackson.core.JsonGenerator; import tools.jackson.core.json.JsonReadFeature; import tools.jackson.core.StreamReadConstraints; import tools.jackson.core.StreamWriteConstraints; import java.io.*; // Create factory with default settings JsonFactory factory = new JsonFactory(); // Create factory with builder pattern and custom configuration JsonFactory configuredFactory = JsonFactory.builder() .enable(JsonReadFeature.ALLOW_JAVA_COMMENTS) .enable(JsonReadFeature.ALLOW_SINGLE_QUOTES) .enable(JsonReadFeature.ALLOW_UNQUOTED_PROPERTY_NAMES) .streamReadConstraints(StreamReadConstraints.builder() .maxNestingDepth(500) .maxStringLength(10_000_000) .maxNumberLength(500) .build()) .streamWriteConstraints(StreamWriteConstraints.builder() .maxNestingDepth(1000) .build()) .build(); // Create parser from various sources JsonParser parserFromString = factory.createParser("{\"name\":\"value\"}"); JsonParser parserFromBytes = factory.createParser("{\"id\":123}".getBytes()); JsonParser parserFromFile = factory.createParser(new File("data.json")); JsonParser parserFromStream = factory.createParser(new FileInputStream("data.json")); // Create generator to various targets StringWriter sw = new StringWriter(); JsonGenerator genToWriter = factory.createGenerator(sw); ByteArrayOutputStream baos = new ByteArrayOutputStream(); JsonGenerator genToStream = factory.createGenerator(baos); // Remember to close resources parserFromString.close(); genToWriter.close(); ``` --- ## JsonParser - Reading JSON Content `JsonParser` provides streaming (pull) parsing of JSON content. It reads tokens sequentially without loading the entire document into memory, making it ideal for large files. ```java import tools.jackson.core.json.JsonFactory; import tools.jackson.core.JsonParser; import tools.jackson.core.JsonToken; JsonFactory factory = new JsonFactory(); String json = """ { "name": "John Doe", "age": 30, "active": true, "scores": [95, 87, 92], "address": { "city": "New York", "zip": "10001" } } """; try (JsonParser parser = factory.createParser(json)) { // Iterate through all tokens while (parser.nextToken() != null) { JsonToken token = parser.currentToken(); switch (token) { case START_OBJECT -> System.out.println("Object started"); case END_OBJECT -> System.out.println("Object ended"); case START_ARRAY -> System.out.println("Array started"); case END_ARRAY -> System.out.println("Array ended"); case PROPERTY_NAME -> System.out.println("Property: " + parser.currentName()); case VALUE_STRING -> System.out.println("String value: " + parser.getString()); case VALUE_NUMBER_INT -> System.out.println("Int value: " + parser.getIntValue()); case VALUE_NUMBER_FLOAT -> System.out.println("Float value: " + parser.getDoubleValue()); case VALUE_TRUE, VALUE_FALSE -> System.out.println("Boolean: " + parser.getBooleanValue()); case VALUE_NULL -> System.out.println("Null value"); default -> { } } } } // Quick access methods for common patterns try (JsonParser p = factory.createParser(json)) { while (p.nextToken() != JsonToken.END_OBJECT) { String fieldName = p.currentName(); p.nextToken(); // move to value if ("name".equals(fieldName)) { String name = p.getString(); } else if ("age".equals(fieldName)) { int age = p.getIntValue(); } else if ("active".equals(fieldName)) { boolean active = parser.getBooleanValue(); } else if ("scores".equals(fieldName)) { // Skip array content p.skipChildren(); } } } ``` --- ## JsonGenerator - Writing JSON Content `JsonGenerator` provides streaming generation of JSON content. It writes tokens sequentially, handling proper escaping, formatting, and structure validation. ```java import tools.jackson.core.json.JsonFactory; import tools.jackson.core.JsonGenerator; import java.io.StringWriter; import java.math.BigDecimal; JsonFactory factory = new JsonFactory(); StringWriter writer = new StringWriter(); try (JsonGenerator gen = factory.createGenerator(writer)) { gen.writeStartObject(); // Write simple properties gen.writeStringProperty("name", "John Doe"); gen.writeNumberProperty("age", 30); gen.writeBooleanProperty("active", true); gen.writeNullProperty("middleName"); // Write array gen.writeArrayPropertyStart("scores"); gen.writeNumber(95); gen.writeNumber(87); gen.writeNumber(92); gen.writeEndArray(); // Write nested object gen.writeObjectPropertyStart("address"); gen.writeStringProperty("city", "New York"); gen.writeStringProperty("zip", "10001"); gen.writeNumberProperty("lat", new BigDecimal("40.7128")); gen.writeEndObject(); // Write array of numbers directly gen.writeName("rawScores"); gen.writeArray(new int[]{100, 99, 98}, 0, 3); gen.writeEndObject(); } System.out.println(writer.toString()); // Output: {"name":"John Doe","age":30,"active":true,"middleName":null, // "scores":[95,87,92],"address":{"city":"New York","zip":"10001", // "lat":40.7128},"rawScores":[100,99,98]} ``` --- ## StreamReadConstraints - Security Limits for Parsing `StreamReadConstraints` protects against malicious input by setting limits on parsing depth, string lengths, and other potentially dangerous input patterns. ```java import tools.jackson.core.StreamReadConstraints; import tools.jackson.core.json.JsonFactory; // Create custom constraints StreamReadConstraints constraints = StreamReadConstraints.builder() .maxNestingDepth(100) // Max object/array nesting depth (default: 500) .maxStringLength(1_000_000) // Max string value length (default: 20,000,000) .maxNumberLength(200) // Max number length in chars (default: 1000) .maxNameLength(10_000) // Max property name length (default: 50,000) .maxDocumentLength(50_000_000L) // Max total document length (default: unlimited) .maxTokenCount(100_000L) // Max token count (default: unlimited) .build(); // Apply to factory JsonFactory factory = JsonFactory.builder() .streamReadConstraints(constraints) .build(); // Override global defaults (use sparingly, affects all default factories) StreamReadConstraints.overrideDefaultStreamReadConstraints( StreamReadConstraints.builder() .maxNestingDepth(200) .build() ); // Access current constraints from parser try (var parser = factory.createParser("{}")) { StreamReadConstraints activeConstraints = parser.streamReadConstraints(); System.out.println("Max depth: " + activeConstraints.getMaxNestingDepth()); System.out.println("Max string length: " + activeConstraints.getMaxStringLen()); } ``` --- ## StreamReadFeature - Parser Configuration Options `StreamReadFeature` provides format-agnostic configuration options for parser behavior, including auto-close behavior, duplicate detection, and performance optimizations. ```java import tools.jackson.core.StreamReadFeature; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.JsonReadFeature; // Build factory with stream read features JsonFactory factory = JsonFactory.builder() // Auto-close source when parser closes (default: enabled) .enable(StreamReadFeature.AUTO_CLOSE_SOURCE) // Detect duplicate property names (default: disabled, adds ~20-30% overhead) .enable(StreamReadFeature.STRICT_DUPLICATE_DETECTION) // Include source info in error locations (default: disabled for security) .enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION) // Use fast double parser (default: enabled in 3.x) .enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER) // Use fast BigDecimal/BigInteger parser (default: enabled in 3.x) .enable(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER) // JSON-specific features .enable(JsonReadFeature.ALLOW_JAVA_COMMENTS) // Allow /* */ and // comments .enable(JsonReadFeature.ALLOW_SINGLE_QUOTES) // Allow 'string' instead of "string" .enable(JsonReadFeature.ALLOW_UNQUOTED_PROPERTY_NAMES) // Allow {key: "value"} .enable(JsonReadFeature.ALLOW_TRAILING_COMMA) // Allow [1,2,3,] .enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS) // Allow NaN, Infinity .build(); // Check if feature is enabled boolean autoClose = factory.isEnabled(StreamReadFeature.AUTO_CLOSE_SOURCE); boolean allowComments = factory.isEnabled(JsonReadFeature.ALLOW_JAVA_COMMENTS); ``` --- ## StreamWriteFeature - Generator Configuration Options `StreamWriteFeature` provides format-agnostic configuration options for generator behavior, controlling output formatting and resource management. ```java import tools.jackson.core.StreamWriteFeature; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.JsonWriteFeature; import tools.jackson.core.util.DefaultPrettyPrinter; JsonFactory factory = JsonFactory.builder() // Auto-close target when generator closes (default: enabled) .enable(StreamWriteFeature.AUTO_CLOSE_TARGET) // Flush output after writeEndObject/writeEndArray (default: disabled) .enable(StreamWriteFeature.FLUSH_PASSED_TO_STREAM) // JSON-specific write features .enable(JsonWriteFeature.QUOTE_PROPERTY_NAMES) // Quote all property names .enable(JsonWriteFeature.WRITE_NAN_AS_STRINGS) // Write NaN/Infinity as strings .enable(JsonWriteFeature.ESCAPE_FORWARD_SLASHES) // Escape / as \/ .disable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS) // Keep numbers as numbers .build(); // Pretty printing StringWriter sw = new StringWriter(); try (JsonGenerator gen = factory.createGenerator(sw)) { // Use default pretty printer gen.setPrettyPrinter(new DefaultPrettyPrinter()); gen.writeStartObject(); gen.writeStringProperty("name", "formatted"); gen.writeArrayPropertyStart("items"); gen.writeNumber(1); gen.writeNumber(2); gen.writeEndArray(); gen.writeEndObject(); } System.out.println(sw.toString()); // Output: // { // "name" : "formatted", // "items" : [ 1, 2 ] // } ``` --- ## Non-Blocking (Async) Parsing Jackson Core supports non-blocking parsing for reactive applications, allowing incremental feeding of data without blocking I/O. ```java import tools.jackson.core.json.JsonFactory; import tools.jackson.core.JsonParser; import tools.jackson.core.JsonToken; import tools.jackson.core.async.ByteArrayFeeder; import tools.jackson.core.ObjectReadContext; import java.nio.charset.StandardCharsets; JsonFactory factory = new JsonFactory(); // Create non-blocking parser JsonParser parser = factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); ByteArrayFeeder feeder = (ByteArrayFeeder) parser.nonBlockingInputFeeder(); // Simulate receiving data in chunks String[] chunks = { "{\"na", "me\":\"Jo", "hn\",\"ag", "e\":30}" }; for (String chunk : chunks) { byte[] bytes = chunk.getBytes(StandardCharsets.UTF_8); feeder.feedInput(bytes, 0, bytes.length); // Process available tokens JsonToken token; while ((token = parser.nextToken()) != JsonToken.NOT_AVAILABLE) { if (token == null) break; // End of input switch (token) { case PROPERTY_NAME -> System.out.println("Field: " + parser.currentName()); case VALUE_STRING -> System.out.println("String: " + parser.getString()); case VALUE_NUMBER_INT -> System.out.println("Number: " + parser.getIntValue()); default -> { } } } } // Signal end of input feeder.endOfInput(); // Process remaining tokens JsonToken token; while ((token = parser.nextToken()) != null) { System.out.println("Final token: " + token); } parser.close(); ``` --- ## Copying and Transforming JSON Jackson Core provides methods to copy JSON content between parsers and generators, useful for filtering or transforming JSON streams. ```java import tools.jackson.core.json.JsonFactory; import tools.jackson.core.JsonParser; import tools.jackson.core.JsonGenerator; import tools.jackson.core.JsonToken; import java.io.StringWriter; JsonFactory factory = new JsonFactory(); String input = """ { "keep": "this", "remove": "that", "nested": {"a": 1, "b": 2} } """; StringWriter output = new StringWriter(); try (JsonParser parser = factory.createParser(input); JsonGenerator gen = factory.createGenerator(output)) { // Copy entire structure parser.nextToken(); // Move to START_OBJECT gen.copyCurrentStructure(parser); } System.out.println("Full copy: " + output); // Selective copying (filtering) output = new StringWriter(); try (JsonParser parser = factory.createParser(input); JsonGenerator gen = factory.createGenerator(output)) { gen.writeStartObject(); while (parser.nextToken() != JsonToken.END_OBJECT) { String fieldName = parser.currentName(); parser.nextToken(); // Move to value // Skip fields starting with "remove" if (fieldName != null && !fieldName.startsWith("remove")) { gen.writeName(fieldName); gen.copyCurrentStructure(parser); // Copy value (handles nested structures) } else { parser.skipChildren(); // Skip value if it's object/array } } gen.writeEndObject(); } System.out.println("Filtered: " + output); // Output: {"keep":"this","nested":{"a":1,"b":2}} ``` --- ## Binary Data Handling (Base64) Jackson Core supports reading and writing binary data as Base64-encoded strings, with multiple Base64 variants available. ```java import tools.jackson.core.json.JsonFactory; import tools.jackson.core.JsonParser; import tools.jackson.core.JsonGenerator; import tools.jackson.core.Base64Variants; import java.io.StringWriter; import java.util.Arrays; JsonFactory factory = new JsonFactory(); byte[] binaryData = {0x00, 0x01, 0x02, (byte)0xFF, (byte)0xFE}; // Writing binary data StringWriter sw = new StringWriter(); try (JsonGenerator gen = factory.createGenerator(sw)) { gen.writeStartObject(); // Default Base64 (MIME without linefeeds) gen.writeBinaryProperty("data", binaryData); // Explicit Base64 variant gen.writeName("dataUrlSafe"); gen.writeBinary(Base64Variants.MODIFIED_FOR_URL, binaryData, 0, binaryData.length); gen.writeEndObject(); } System.out.println(sw.toString()); // Output: {"data":"AAEC//4=","dataUrlSafe":"AAEC__4"} // Reading binary data String json = sw.toString(); try (JsonParser parser = factory.createParser(json)) { parser.nextToken(); // START_OBJECT while (parser.nextToken() != null) { if ("data".equals(parser.currentName())) { parser.nextToken(); byte[] decoded = parser.getBinaryValue(); System.out.println("Decoded: " + Arrays.toString(decoded)); // Output: Decoded: [0, 1, 2, -1, -2] } } } ``` --- ## JsonPointer - Navigating JSON Structure `JsonPointer` implements RFC 6901 for referencing specific values within a JSON document. ```java import tools.jackson.core.JsonPointer; // Create pointers JsonPointer root = JsonPointer.empty(); JsonPointer namePtr = JsonPointer.compile("/name"); JsonPointer nestedPtr = JsonPointer.compile("/address/city"); JsonPointer arrayElement = JsonPointer.compile("/scores/0"); JsonPointer escapedKey = JsonPointer.compile("/foo~1bar"); // Key contains / JsonPointer tildeKey = JsonPointer.compile("/foo~0bar"); // Key contains ~ // Pointer operations System.out.println("Matches root: " + namePtr.matches()); // false System.out.println("First segment: " + nestedPtr.getMatchingProperty()); // "address" System.out.println("Remaining: " + nestedPtr.tail()); // /city // Building pointers programmatically JsonPointer base = JsonPointer.compile("/data"); JsonPointer extended = base.appendProperty("items").appendIndex(0); System.out.println("Extended: " + extended); // /data/items/0 // Head operation (parent path) JsonPointer child = JsonPointer.compile("/a/b/c"); JsonPointer parent = child.head(); // /a/b System.out.println("Parent: " + parent); ``` --- ## Error Handling and Location Tracking Jackson Core provides detailed error information including precise location tracking for parse errors. ```java import tools.jackson.core.json.JsonFactory; import tools.jackson.core.JsonParser; import tools.jackson.core.TokenStreamLocation; import tools.jackson.core.exc.StreamReadException; import tools.jackson.core.ErrorReportConfiguration; // Configure error reporting JsonFactory factory = JsonFactory.builder() .errorReportConfiguration(ErrorReportConfiguration.builder() .maxErrorTokenLength(256) // Max length of token in error messages .maxRawContentLength(500) // Max raw content shown in errors .build()) .build(); String invalidJson = """ { "name": "test", "value": invalid_token } """; try (JsonParser parser = factory.createParser(invalidJson)) { while (parser.nextToken() != null) { // Process tokens TokenStreamLocation loc = parser.currentTokenLocation(); System.out.printf("Token at line %d, column %d%n", loc.getLineNr(), loc.getColumnNr()); } } catch (StreamReadException e) { // Detailed error information System.out.println("Parse error: " + e.getMessage()); TokenStreamLocation loc = e.getLocation(); if (loc != null) { System.out.printf("Error at line %d, column %d%n", loc.getLineNr(), loc.getColumnNr()); System.out.println("Byte offset: " + loc.getByteOffset()); System.out.println("Char offset: " + loc.getCharOffset()); } } ``` --- ## Token Context and Streaming Context Track the current parsing context (object, array, root level) during streaming operations. ```java import tools.jackson.core.json.JsonFactory; import tools.jackson.core.JsonParser; import tools.jackson.core.JsonToken; import tools.jackson.core.TokenStreamContext; JsonFactory factory = new JsonFactory(); String json = """ { "users": [ {"name": "Alice", "age": 30}, {"name": "Bob", "age": 25} ] } """; try (JsonParser parser = factory.createParser(json)) { while (parser.nextToken() != null) { TokenStreamContext ctx = parser.streamReadContext(); // Context information System.out.printf("Type: %s, Depth: %d, Index: %d, Name: %s%n", ctx.inObject() ? "Object" : (ctx.inArray() ? "Array" : "Root"), ctx.getNestingDepth(), ctx.getCurrentIndex(), ctx.currentName() ); // Build JSON Pointer for current location JsonPointer ptr = ctx.pathAsPointer(); System.out.println("Path: " + ptr); } } ``` --- ## Summary Jackson Core provides the essential building blocks for high-performance JSON processing in Java. The streaming API (`JsonParser`/`JsonGenerator`) enables memory-efficient processing of large documents, while the factory pattern (`JsonFactory`) ensures thread-safe, reusable configuration. Key use cases include: REST API payload processing, large file transformation, real-time data streaming, and building custom JSON-based protocols. For most applications, Jackson Core serves as the foundation layer with [Jackson Databind](https://github.com/FasterXML/jackson-databind) providing higher-level object mapping on top. Direct use of the streaming API is recommended when maximum performance is required, when processing documents larger than available memory, or when building format converters between JSON and other formats. The library integrates seamlessly with reactive frameworks through its non-blocking parser API, and its configurable security constraints protect against denial-of-service attacks from malicious input.