or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

admin-jmx.mdansi-support.mdaot-native-image.mdapplication-info.mdavailability.mdbootstrap.mdbootstrapping.mdbuilder.mdcloud-platform.mdconfiguration-annotations.mdconfiguration-data.mdconfiguration-properties.mdconversion.mddiagnostics.mdenvironment-property-sources.mdindex.mdjson-support.mdlifecycle-events.mdlogging.mdorigin-tracking.mdresource-loading.mdretry-support.mdssl-tls.mdstartup-metrics.mdsupport.mdsystem-utilities.mdtask-execution.mdthreading.mdutilities.mdvalidation.mdweb-support.md
tile.json

json-support.mddocs/

JSON Support

Quick Reference

This documentation has been enhanced for AI coding agents with comprehensive examples, complete API signatures, thread safety notes, error handling patterns, and production-ready usage patterns.

Package: org.springframework.boot.json Module: org.springframework.boot:spring-boot Since: 1.0.0

Spring Boot provides abstraction over multiple JSON parsing libraries with a unified API. This allows applications to parse JSON without depending on specific implementations, with automatic detection and selection of available JSON libraries.

Overview

The JSON support system provides:

  • JsonParser: Unified interface for JSON parsing
  • Multiple Implementations: Jackson, Gson, and Basic
  • Auto-detection: Automatically selects best available parser
  • Type-safe Parsing: Converts JSON to Java types

Core Interface

JsonParser

package org.springframework.boot.json;

import java.util.List;
import java.util.Map;

/**
 * Interface to parse JSON into Map or List.
 *
 * Thread Safety: Implementations should be thread-safe.
 *
 * @since 1.0.0
 */
public interface JsonParser {

    /**
     * Parse the specified JSON string into a Map.
     *
     * @param json the JSON to parse (may be null)
     * @return the parsed JSON as a map
     * @throws JsonParseException if the JSON cannot be parsed
     */
    Map<String, Object> parseMap(@Nullable String json);

    /**
     * Parse the specified JSON string into a List.
     *
     * @param json the JSON to parse (may be null)
     * @return the parsed JSON as a list
     * @throws JsonParseException if the JSON cannot be parsed
     */
    List<Object> parseList(@Nullable String json);
}

Available Implementations

JacksonJsonParser

Uses Jackson library (preferred, most feature-rich).

package org.springframework.boot.json;

/**
 * JsonParser implementation using Jackson.
 * Requires tools.jackson.databind:jackson-databind on classpath (Jackson 3.x).
 *
 * Thread Safety: Thread-safe.
 *
 * @since 1.0.0
 */
public class JacksonJsonParser extends AbstractJsonParser {

    /**
     * Creates a JacksonJsonParser using the default JsonMapper configuration.
     */
    public JacksonJsonParser() {
        // Uses default JsonMapper internally
    }

    /**
     * Creates a JacksonJsonParser with a custom JsonMapper.
     * Allows full control over Jackson configuration (modules, features, etc.).
     *
     * @param jsonMapper the JsonMapper to use for parsing
     * @since 1.0.0
     */
    public JacksonJsonParser(JsonMapper jsonMapper) {
        // Uses provided JsonMapper for parsing
    }
}

GsonJsonParser

Uses Google's Gson library.

package org.springframework.boot.json;

/**
 * JsonParser implementation using Gson.
 * Requires com.google.code.gson:gson on classpath.
 *
 * Thread Safety: Thread-safe.
 *
 * @since 1.2.0
 */
public class GsonJsonParser extends AbstractJsonParser {

    public GsonJsonParser() {
        // Uses Gson internally
    }
}

BasicJsonParser

Minimal built-in parser requiring no dependencies.

package org.springframework.boot.json;

/**
 * Basic JSON parser using only JDK classes.
 * No external dependencies required.
 * Limited feature set - handles simple JSON only.
 *
 * Thread Safety: Thread-safe.
 *
 * @since 1.2.0
 */
public class BasicJsonParser extends AbstractJsonParser {

    public BasicJsonParser() {
        // No dependencies
    }
}

AbstractJsonParser

Abstract base class for JsonParser implementations providing common parsing utilities and error handling.

package org.springframework.boot.json;

import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Function;
import org.springframework.lang.Nullable;

/**
 * Abstract base class for JsonParser implementations.
 * Provides common utility methods for parsing JSON strings into Maps and Lists,
 * with consistent null handling and error wrapping.
 *
 * Thread Safety: Thread-safe when subclass implementations are thread-safe.
 *
 * @since 2.0.1
 */
public abstract class AbstractJsonParser implements JsonParser {

    /**
     * Parse a JSON string into a Map using the provided parser function.
     * Handles null input and trims whitespace before parsing.
     *
     * @param json the JSON string to parse (may be null)
     * @param parser the function to parse the trimmed JSON string
     * @return the parsed Map, or an empty Map if json is null or empty
     */
    protected final Map<String, Object> parseMap(
            @Nullable String json,
            Function<String, Map<String, Object>> parser) {
        // Implementation delegates to subclass parser function
        return null; // Placeholder
    }

    /**
     * Parse a JSON string into a List using the provided parser function.
     * Handles null input and trims whitespace before parsing.
     *
     * @param json the JSON string to parse (may be null)
     * @param parser the function to parse the trimmed JSON string
     * @return the parsed List, or an empty List if json is null or empty
     */
    protected final List<Object> parseList(
            @Nullable String json,
            Function<String, List<Object>> parser) {
        // Implementation delegates to subclass parser function
        return null; // Placeholder
    }

    /**
     * Trim whitespace and optional prefix from JSON string before parsing.
     * Useful for handling JSON with BOM or other prefixes.
     *
     * @param json the JSON string to parse (may be null)
     * @param prefix optional prefix to remove (may be null)
     * @param parser the function to parse the processed JSON string
     * @param <T> the return type
     * @return the parsed result
     */
    protected final <T> T trimParse(
            @Nullable String json,
            String prefix,
            Function<String, T> parser) {
        // Implementation trims and removes prefix before delegating to parser
        return null; // Placeholder
    }

    /**
     * Try to parse JSON and wrap any checked exceptions as JsonParseException.
     * Provides consistent error handling across all parser implementations.
     *
     * @param parser the parsing operation to attempt
     * @param check the exception type to catch and wrap
     * @param <T> the return type
     * @return the parsed result
     * @throws JsonParseException if parsing fails
     */
    protected final <T> T tryParse(
            Callable<T> parser,
            Class<? extends Exception> check) {
        // Implementation tries parsing and wraps exceptions
        return null; // Placeholder
    }
}

Usage Example - Extending AbstractJsonParser:

import org.springframework.boot.json.AbstractJsonParser;
import org.springframework.boot.json.JsonParseException;
import java.util.List;
import java.util.Map;
import com.example.CustomJsonLibrary;

/**
 * Custom JSON parser implementation using a third-party library.
 */
public class CustomJsonParser extends AbstractJsonParser {

    private final CustomJsonLibrary library = new CustomJsonLibrary();

    @Override
    public Map<String, Object> parseMap(String json) {
        // Use AbstractJsonParser utility for consistent null handling
        return parseMap(json, (trimmedJson) -> {
            try {
                return library.parseToMap(trimmedJson);
            } catch (Exception ex) {
                throw new JsonParseException(ex);
            }
        });
    }

    @Override
    public List<Object> parseList(String json) {
        // Use AbstractJsonParser utility for consistent null handling
        return parseList(json, (trimmedJson) -> {
            try {
                return library.parseToList(trimmedJson);
            } catch (Exception ex) {
                throw new JsonParseException(ex);
            }
        });
    }
}

JsonParserFactory

Factory class for obtaining the most appropriate JsonParser implementation available on the classpath. Automatically detects and returns the best available parser.

Class Definition

package org.springframework.boot.json;

/**
 * Factory to create a JsonParser.
 * Tries Jackson, then Gson, then falls back to BasicJsonParser.
 *
 * @since 1.0.0
 */
public abstract class JsonParserFactory {

    /**
     * Static factory for the "best" JSON parser available on the classpath.
     * Detection order:
     * 1. Jackson (tools.jackson.databind.ObjectMapper)
     * 2. Gson (com.google.gson.Gson)
     * 3. BasicJsonParser (fallback, no dependencies)
     *
     * @return a JsonParser instance
     */
    public static JsonParser getJsonParser();
}

Parser Selection Logic

The factory uses classpath detection to select the parser:

  1. Jackson - If tools.jackson.databind.ObjectMapper is present
  2. Gson - If com.google.gson.Gson is present
  3. BasicJsonParser - Always available as fallback (no dependencies)

Usage Examples

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import java.util.Map;

// Example 1: Simple usage
public class SimpleParserUsage {
    public void parseJson(String json) {
        JsonParser parser = JsonParserFactory.getJsonParser();
        Map<String, Object> result = parser.parseMap(json);
        System.out.println("Parser type: " + parser.getClass().getSimpleName());
    }
}

// Example 2: Singleton pattern
@Component
public class JsonService {
    private final JsonParser parser = JsonParserFactory.getJsonParser();

    public Map<String, Object> parse(String json) {
        return parser.parseMap(json);
    }

    public List<Object> parseArray(String json) {
        return parser.parseList(json);
    }
}

// Example 3: Check which parser is used
public class ParserDetection {
    public static void main(String[] args) {
        JsonParser parser = JsonParserFactory.getJsonParser();

        if (parser instanceof JacksonJsonParser) {
            System.out.println("Using Jackson parser (best performance)");
        } else if (parser instanceof GsonJsonParser) {
            System.out.println("Using Gson parser");
        } else if (parser instanceof BasicJsonParser) {
            System.out.println("Using BasicJsonParser (fallback)");
        }
    }
}

Best Practice: Create a single JsonParser instance via JsonParserFactory.getJsonParser() and reuse it throughout your application. All implementations are thread-safe.

JsonParseException

Runtime exception thrown when JSON parsing fails.

package org.springframework.boot.json;

/**
 * Exception thrown when JSON parsing fails.
 * Wraps underlying parsing exceptions from various JSON libraries
 * providing a consistent exception type across all parser implementations.
 *
 * Thread Safety: Thread-safe (immutable after construction).
 *
 * @since 2.0.1
 */
public class JsonParseException extends IllegalArgumentException {

    /**
     * Creates a new JsonParseException with default message.
     */
    public JsonParseException() {
        this(null);
    }

    /**
     * Creates a new JsonParseException with the specified cause.
     * The detail message will be "Cannot parse JSON".
     *
     * @param cause the underlying cause (may be null)
     */
    public JsonParseException(@Nullable Throwable cause) {
        super("Cannot parse JSON", cause);
    }
}

Usage Example:

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
import java.util.Map;

public class ErrorHandlingExample {

    private final JsonParser parser = JsonParserFactory.getJsonParser();

    public Map<String, Object> parseWithFallback(String json, Map<String, Object> fallback) {
        try {
            return parser.parseMap(json);
        } catch (JsonParseException e) {
            System.err.println("JSON parsing failed: " + e.getMessage());
            if (e.getCause() != null) {
                System.err.println("Caused by: " + e.getCause().getMessage());
            }
            return fallback;
        }
    }

    public void parseStrict(String json) {
        try {
            Map<String, Object> result = parser.parseMap(json);
            processResult(result);
        } catch (JsonParseException e) {
            // Re-throw with more context
            throw new IllegalArgumentException(
                "Invalid JSON configuration: " + json.substring(0, Math.min(50, json.length())),
                e
            );
        }
    }

    private void processResult(Map<String, Object> result) {
        // Process parsed result
    }
}

JSON Writing

Spring Boot provides interfaces for writing JSON output without requiring a full marshalling library. The JsonWriter and WritableJson interfaces enable lightweight JSON generation with configurable member mapping.

JsonWriter<T>

Interface for writing JSON output. Typically used when dependencies on fully-featured marshalling libraries (like Jackson or Gson) cannot be assumed.

package org.springframework.boot.json;

import java.io.IOException;
import java.util.function.Consumer;

import org.jspecify.annotations.Nullable;

/**
 * Interface that can be used to write JSON output.
 *
 * Thread Safety: Implementations are typically thread-safe.
 *
 * @param <T> the type being written
 * @since 3.4.0
 */
@FunctionalInterface
public interface JsonWriter<T> {

    /**
     * Write the given instance to the provided {@link Appendable}.
     *
     * @param instance the instance to write (may be {@code null})
     * @param out the output that should receive the JSON
     * @throws IOException on IO error
     */
    void write(@Nullable T instance, Appendable out) throws IOException;

    /**
     * Write the given instance to a JSON string.
     *
     * @param instance the instance to write (may be {@code null})
     * @return the JSON string
     */
    default String writeToString(@Nullable T instance);

    /**
     * Provide a {@link WritableJson} implementation that may be used to write
     * the given instance to various outputs.
     *
     * @param instance the instance to write (may be {@code null})
     * @return a {@link WritableJson} instance that may be used to write the JSON
     */
    default WritableJson write(@Nullable T instance);

    /**
     * Return a new {@link JsonWriter} instance that appends a new line after
     * the JSON has been written.
     *
     * @return a new {@link JsonWriter} instance that appends a new line
     */
    default JsonWriter<T> withNewLineAtEnd();

    /**
     * Return a new {@link JsonWriter} instance that appends the given suffix
     * after the JSON has been written.
     *
     * @param suffix the suffix to write, if any
     * @return a new {@link JsonWriter} instance that appends a suffix
     */
    default JsonWriter<T> withSuffix(@Nullable String suffix);

    /**
     * Factory method to return a {@link JsonWriter} for standard Java types.
     * Supports String, Number, Boolean, Collection, Array, Map, and WritableJson.
     *
     * @param <T> the type to write
     * @return a {@link JsonWriter} instance
     */
    static <T> JsonWriter<T> standard();

    /**
     * Factory method to return a {@link JsonWriter} with specific member mapping.
     *
     * @param <T> the type to write
     * @param members a consumer which should configure the members
     * @return a {@link JsonWriter} instance
     * @see Members
     */
    static <T> JsonWriter<T> of(Consumer<Members<T>> members);
}

Key Features:

  • Standard Types: Built-in support for Java primitives, collections, maps, and arrays
  • Custom Mapping: Configure specific member extraction and transformation
  • Fluent API: Chain configuration methods for readability
  • Null Handling: Supports nullable instances and values

WritableJson

Interface representing JSON content that can be written to various outputs.

package org.springframework.boot.json;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;

import org.springframework.core.io.WritableResource;

/**
 * JSON content that can be written out.
 *
 * Thread Safety: Implementations should be stateless and thread-safe.
 *
 * @since 3.4.0
 * @see JsonWriter
 */
@FunctionalInterface
public interface WritableJson {

    /**
     * Write the JSON to the provided {@link Appendable}.
     *
     * @param out the {@link Appendable} to receive the JSON
     * @throws IOException on IO error
     */
    void to(Appendable out) throws IOException;

    /**
     * Write the JSON to a {@link String}.
     *
     * @return the JSON string
     */
    default String toJsonString();

    /**
     * Write the JSON to a UTF-8 encoded byte array.
     *
     * @return the JSON bytes
     */
    default byte[] toByteArray();

    /**
     * Write the JSON to a byte array with specified charset.
     *
     * @param charset the charset
     * @return the JSON bytes
     */
    default byte[] toByteArray(Charset charset);

    /**
     * Write the JSON to the provided {@link WritableResource} using UTF-8 encoding.
     *
     * @param out the {@link WritableResource} to receive the JSON
     * @throws IOException on IO error
     */
    default void toResource(WritableResource out) throws IOException;

    /**
     * Write the JSON to the provided {@link WritableResource} using given charset.
     *
     * @param out the {@link WritableResource} to receive the JSON
     * @param charset the charset to use
     * @throws IOException on IO error
     */
    default void toResource(WritableResource out, Charset charset) throws IOException;

    /**
     * Write the JSON to the provided {@link OutputStream} using UTF-8 encoding.
     * The output stream will not be closed.
     *
     * @param out the {@link OutputStream} to receive the JSON
     * @throws IOException on IO error
     */
    default void toOutputStream(OutputStream out) throws IOException;

    /**
     * Write the JSON to the provided {@link OutputStream} using given charset.
     * The output stream will not be closed.
     *
     * @param out the {@link OutputStream} to receive the JSON
     * @param charset the charset to use
     * @throws IOException on IO error
     */
    default void toOutputStream(OutputStream out, Charset charset) throws IOException;

    /**
     * Write the JSON to the provided {@link Writer}.
     * The writer will be flushed but not closed.
     *
     * @param out the {@link Writer} to receive the JSON
     * @throws IOException on IO error
     */
    default void toWriter(Writer out) throws IOException;

    /**
     * Factory method to create a {@link WritableJson} with sensible toString().
     *
     * @param writableJson the source {@link WritableJson}
     * @return a new {@link WritableJson} with sensible {@link Object#toString()}
     */
    static WritableJson of(WritableJson writableJson);
}

JsonWriter Members Configuration

The JsonWriter.Members class provides a fluent API for configuring JSON member mapping.

Common Methods:

  • add(String name, Extractor<T, V> extractor) - Add named member with value extraction
  • add(String name) - Add member with access to full instance
  • from(Extractor<T, V> extractor).usingMembers(...) - Configure nested members
  • addMapEntries(Extractor<T, Map<K,V>> extractor) - Add all map entries to JSON
  • Member.whenNotNull() - Only include member when value is not null
  • Member.as(Extractor<T, R> adapter) - Transform value to different type
  • Member.when(Predicate<T> predicate) - Conditionally include member

Writing Standard Types

package com.example.json;

import org.springframework.boot.json.JsonWriter;
import org.springframework.boot.json.WritableJson;

import java.util.Map;
import java.util.List;

/**
 * Writing standard Java types to JSON.
 */
public class StandardJsonWriting {

    public void writeStandardTypes() throws Exception {
        // Create writer for standard types
        JsonWriter<Map<String, Object>> writer = JsonWriter.standard();

        // Write a map to string
        Map<String, Object> data = Map.of(
            "name", "Spring Boot",
            "version", "4.0.0",
            "active", true,
            "components", List.of("core", "web", "data")
        );

        String json = writer.writeToString(data);
        System.out.println(json);
        // Output: {"name":"Spring Boot","version":"4.0.0","active":true,"components":["core","web","data"]}

        // Write to appendable
        StringBuilder sb = new StringBuilder();
        writer.write(data, sb);

        // Get WritableJson for multiple outputs
        WritableJson writable = writer.write(data);
        byte[] bytes = writable.toByteArray();
        String str = writable.toJsonString();
    }
}

Custom Object Mapping

package com.example.json;

import org.springframework.boot.json.JsonWriter;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

/**
 * Custom object mapping with member configuration.
 */
public class CustomObjectMapping {

    record Person(String firstName, String lastName, LocalDate dateOfBirth, String email) {}

    public void writeCustomObject() {
        // Configure custom member mapping
        JsonWriter<Person> writer = JsonWriter.of((members) -> {
            members.add("first", Person::firstName);
            members.add("last", Person::lastName);
            members.add("dob", Person::dateOfBirth)
                .whenNotNull()
                .as(DateTimeFormatter.ISO_DATE::format);
            members.add("email", Person::email)
                .whenNotNull();
        });

        Person person = new Person("John", "Doe", LocalDate.of(1990, 5, 15), "john@example.com");
        String json = writer.writeToString(person);
        System.out.println(json);
        // Output: {"first":"John","last":"Doe","dob":"1990-05-15","email":"john@example.com"}

        // Null handling
        Person personWithoutEmail = new Person("Jane", "Smith", null, null);
        String json2 = writer.writeToString(personWithoutEmail);
        System.out.println(json2);
        // Output: {"first":"Jane","last":"Smith"}
    }
}

Nested Object Writing

package com.example.json;

import org.springframework.boot.json.JsonWriter;

import java.util.List;

/**
 * Writing nested objects with member configuration.
 */
public class NestedObjectWriting {

    record Address(String street, String city, String country) {}
    record User(String name, Address address, List<String> tags) {}

    public void writeNestedObjects() {
        JsonWriter<User> writer = JsonWriter.of((members) -> {
            members.add("name", User::name);

            // Nested object with its own member configuration
            members.add("address", User::address)
                .whenNotNull()
                .usingMembers((addressMembers) -> {
                    addressMembers.add("street", Address::street);
                    addressMembers.add("city", Address::city);
                    addressMembers.add("country", Address::country);
                });

            // Array member
            members.add("tags", User::tags)
                .whenNotEmpty();
        });

        User user = new User(
            "Alice",
            new Address("123 Main St", "Springfield", "USA"),
            List.of("admin", "developer")
        );

        String json = writer.writeToString(user);
        System.out.println(json);
        // Output: {"name":"Alice","address":{"street":"123 Main St","city":"Springfield","country":"USA"},"tags":["admin","developer"]}
    }
}

Writing Map Entries

package com.example.json;

import org.springframework.boot.json.JsonWriter;

import java.util.Map;

/**
 * Writing map entries as JSON pairs.
 */
public class MapEntriesWriting {

    record Event(String name, Map<String, String> labels) {}

    public void writeMapEntries() {
        // Add map entries as named object
        JsonWriter<Event> writer1 = JsonWriter.of((members) -> {
            members.add("name", Event::name);
            members.add("labels", Event::labels)
                .usingPairs(Map::forEach);
        });

        Event event = new Event("deployment", Map.of("env", "prod", "version", "1.0"));
        String json1 = writer1.writeToString(event);
        System.out.println(json1);
        // Output: {"name":"deployment","labels":{"env":"prod","version":"1.0"}}

        // Add map entries to existing JSON object (unnamed member)
        JsonWriter<Event> writer2 = JsonWriter.of((members) -> {
            members.add("name", Event::name);
            members.from(Event::labels)
                .usingPairs(Map::forEach);  // No name, merges into parent object
        });

        String json2 = writer2.writeToString(event);
        System.out.println(json2);
        // Output: {"name":"deployment","env":"prod","version":"1.0"}
    }
}

Writing with Suffix

package com.example.json;

import org.springframework.boot.json.JsonWriter;

import java.util.Map;

/**
 * Writing JSON with suffixes (e.g., newlines).
 */
public class JsonWritingWithSuffix {

    public void writeLinesWithNewline() {
        // Add newline after each JSON object (useful for JSONL format)
        JsonWriter<Map<String, Object>> writer = JsonWriter.<Map<String, Object>>standard()
            .withNewLineAtEnd();

        Map<String, Object> record1 = Map.of("id", 1, "name", "Alice");
        Map<String, Object> record2 = Map.of("id", 2, "name", "Bob");

        String line1 = writer.writeToString(record1);
        String line2 = writer.writeToString(record2);

        System.out.println(line1);  // {"id":1,"name":"Alice"}\n
        System.out.println(line2);  // {"id":2,"name":"Bob"}\n
    }
}

WritableJson Output Options

package com.example.json;

import org.springframework.boot.json.JsonWriter;
import org.springframework.boot.json.WritableJson;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.WritableResource;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * Using WritableJson for various output types.
 */
public class WritableJsonOutputs {

    public void writeToVariousOutputs() throws IOException {
        JsonWriter<Map<String, Object>> writer = JsonWriter.standard();
        Map<String, Object> data = Map.of("status", "ok", "code", 200);

        WritableJson writable = writer.write(data);

        // Write to String
        String jsonString = writable.toJsonString();
        System.out.println("String: " + jsonString);

        // Write to byte array (UTF-8)
        byte[] bytes = writable.toByteArray();
        System.out.println("Bytes: " + bytes.length);

        // Write to byte array (custom charset)
        byte[] bytesUtf16 = writable.toByteArray(StandardCharsets.UTF_16);

        // Write to OutputStream
        try (FileOutputStream fos = new FileOutputStream("/tmp/output.json")) {
            writable.toOutputStream(fos);
        }

        // Write to WritableResource
        WritableResource resource = new FileSystemResource("/tmp/data.json");
        writable.toResource(resource);

        // Write to Writer
        try (java.io.FileWriter fw = new java.io.FileWriter("/tmp/writer.json")) {
            writable.toWriter(fw);
        }
    }
}

Conditional Member Inclusion

package com.example.json;

import org.springframework.boot.json.JsonWriter;

/**
 * Conditional member inclusion based on predicates.
 */
public class ConditionalMemberInclusion {

    record Product(String name, Double price, Integer stock, String description) {}

    public void writeWithConditions() {
        JsonWriter<Product> writer = JsonWriter.of((members) -> {
            members.add("name", Product::name);

            // Only include price if not null
            members.add("price", Product::price)
                .whenNotNull();

            // Only include stock if greater than zero
            members.add("stock", Product::stock)
                .when(stock -> stock != null && stock > 0);

            // Only include description if it has content
            members.add("description", Product::description)
                .whenHasLength();
        });

        // All fields present
        Product p1 = new Product("Laptop", 999.99, 5, "High-performance laptop");
        System.out.println(writer.writeToString(p1));
        // Output: {"name":"Laptop","price":999.99,"stock":5,"description":"High-performance laptop"}

        // Some fields missing
        Product p2 = new Product("Mouse", null, 0, "");
        System.out.println(writer.writeToString(p2));
        // Output: {"name":"Mouse"}
    }
}

Value Transformation

package com.example.json;

import org.springframework.boot.json.JsonWriter;

import java.time.Instant;
import java.util.Locale;

/**
 * Transforming values before writing to JSON.
 */
public class ValueTransformation {

    record LogEntry(String message, Instant timestamp, String level) {}

    public void writeWithTransformation() {
        JsonWriter<LogEntry> writer = JsonWriter.of((members) -> {
            members.add("message", LogEntry::message);

            // Transform Instant to ISO-8601 string
            members.add("timestamp", LogEntry::timestamp)
                .as(Instant::toString);

            // Transform level to uppercase
            members.add("level", LogEntry::level)
                .as(level -> level.toUpperCase(Locale.ROOT));
        });

        LogEntry entry = new LogEntry(
            "Application started",
            Instant.parse("2024-01-15T10:30:00Z"),
            "info"
        );

        String json = writer.writeToString(entry);
        System.out.println(json);
        // Output: {"message":"Application started","timestamp":"2024-01-15T10:30:00Z","level":"INFO"}
    }
}

Thread Safety: JsonWriter instances created via factory methods are thread-safe and can be reused across multiple threads. The WritableJson interface is typically implemented as stateless and thread-safe.

Performance: The JsonWriter implementation is optimized for lightweight JSON generation. For complex object graphs with circular references or advanced features, consider using a full JSON library like Jackson.

Best Practices:

  1. Create JsonWriter instances once and reuse them (they are thread-safe)
  2. Use whenNotNull() and whenHasLength() to avoid writing empty fields
  3. Use as() for type transformations rather than manual conversion
  4. Prefer WritableJson when writing to multiple outputs
  5. For log files or streaming, use withNewLineAtEnd() for JSONL format

Usage Examples

Auto-Detection

package com.example.json;

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;

import java.util.Map;
import java.util.List;

/**
 * Automatic parser selection based on classpath.
 */
public class JsonParsingExample {

    public void parseWithAutoDetection() {
        // Automatically selects best available parser:
        // 1. JacksonJsonParser (if Jackson present)
        // 2. GsonJsonParser (if Gson present)
        // 3. BasicJsonParser (always available as fallback)
        JsonParser parser = JsonParserFactory.getJsonParser();

        // Parse JSON object
        String jsonObject = "{\"name\":\"John\",\"age\":30,\"city\":\"New York\"}";
        Map<String, Object> map = parser.parseMap(jsonObject);

        System.out.println("Name: " + map.get("name"));
        System.out.println("Age: " + map.get("age"));
        System.out.println("City: " + map.get("city"));

        // Parse JSON array
        String jsonArray = "[\"apple\",\"banana\",\"cherry\"]";
        List<Object> list = parser.parseList(jsonArray);

        System.out.println("Fruits: " + list);
    }
}

Explicit Parser Selection

package com.example.json;

import org.springframework.boot.json.JacksonJsonParser;
import org.springframework.boot.json.GsonJsonParser;
import org.springframework.boot.json.BasicJsonParser;

/**
 * Explicitly choosing JSON parser implementation.
 */
public class ExplicitParserExample {

    public void useJackson() {
        JacksonJsonParser parser = new JacksonJsonParser();

        String json = "{\"id\":1,\"active\":true}";
        Map<String, Object> data = parser.parseMap(json);

        System.out.println("ID: " + data.get("id"));
        System.out.println("Active: " + data.get("active"));
    }

    public void useGson() {
        GsonJsonParser parser = new GsonJsonParser();

        String json = "[{\"id\":1},{\"id\":2}]";
        List<Object> items = parser.parseList(json);

        System.out.println("Items: " + items.size());
    }

    public void useBasic() {
        // No external dependencies needed
        BasicJsonParser parser = new BasicJsonParser();

        String json = "{\"message\":\"Hello\"}";
        Map<String, Object> result = parser.parseMap(json);

        System.out.println("Message: " + result.get("message"));
    }
}

Parsing Complex JSON

package com.example.json;

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;

import java.util.List;
import java.util.Map;

/**
 * Parsing nested JSON structures.
 */
public class ComplexJsonExample {

    private final JsonParser parser = JsonParserFactory.getJsonParser();

    public void parseNestedObject() {
        String json = """
            {
                "user": {
                    "id": 123,
                    "name": "John Doe",
                    "address": {
                        "street": "123 Main St",
                        "city": "Springfield"
                    }
                }
            }
            """;

        Map<String, Object> root = parser.parseMap(json);

        @SuppressWarnings("unchecked")
        Map<String, Object> user = (Map<String, Object>) root.get("user");

        System.out.println("User ID: " + user.get("id"));
        System.out.println("User Name: " + user.get("name"));

        @SuppressWarnings("unchecked")
        Map<String, Object> address = (Map<String, Object>) user.get("address");
        System.out.println("City: " + address.get("city"));
    }

    public void parseArrayOfObjects() {
        String json = """
            [
                {"id": 1, "name": "Product A", "price": 29.99},
                {"id": 2, "name": "Product B", "price": 39.99},
                {"id": 3, "name": "Product C", "price": 49.99}
            ]
            """;

        List<Object> products = parser.parseList(json);

        for (Object item : products) {
            @SuppressWarnings("unchecked")
            Map<String, Object> product = (Map<String, Object>) item;

            System.out.printf("Product %d: %s - $%.2f%n",
                product.get("id"),
                product.get("name"),
                product.get("price"));
        }
    }
}

Error Handling

package com.example.json;

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParseException;
import org.springframework.boot.json.JsonParserFactory;

/**
 * Proper error handling for JSON parsing.
 */
public class ErrorHandlingExample {

    private final JsonParser parser = JsonParserFactory.getJsonParser();

    public Map<String, Object> parseWithErrorHandling(String json) {
        try {
            return parser.parseMap(json);
        } catch (JsonParseException e) {
            System.err.println("Failed to parse JSON: " + e.getMessage());
            System.err.println("Invalid JSON: " + json);

            // Return empty map or rethrow
            return Map.of();
        }
    }

    public void validateAndParse(String json) {
        if (json == null || json.trim().isEmpty()) {
            throw new IllegalArgumentException("JSON string cannot be null or empty");
        }

        try {
            Map<String, Object> result = parser.parseMap(json);
            processResult(result);
        } catch (JsonParseException e) {
            // Log error with context
            System.err.printf("JSON parse error at position %d: %s%n",
                findErrorPosition(json, e),
                e.getMessage());
            throw e;
        }
    }

    private int findErrorPosition(String json, JsonParseException e) {
        // Extract position from exception message if available
        return 0;
    }

    private void processResult(Map<String, Object> result) {
        // Process parsed JSON
    }
}

Testing with Different Parsers

package com.example.json;

import org.junit.jupiter.api.Test;
import org.springframework.boot.json.*;

import java.util.Map;

import static org.assertj.core.api.Assertions.*;

/**
 * Tests ensuring consistent behavior across parsers.
 */
class JsonParserTest {

    private static final String JSON = "{\"key\":\"value\",\"number\":42}";

    @Test
    void jacksonParsesCorrectly() {
        JsonParser parser = new JacksonJsonParser();

        Map<String, Object> result = parser.parseMap(JSON);

        assertThat(result)
            .containsEntry("key", "value")
            .containsEntry("number", 42);
    }

    @Test
    void gsonParsesCorrectly() {
        JsonParser parser = new GsonJsonParser();

        Map<String, Object> result = parser.parseMap(JSON);

        assertThat(result)
            .containsEntry("key", "value")
            .containsEntry("number", 42.0); // Gson returns Double
    }

    @Test
    void basicParsesCorrectly() {
        JsonParser parser = new BasicJsonParser();

        Map<String, Object> result = parser.parseMap(JSON);

        assertThat(result)
            .containsEntry("key", "value")
            .containsEntry("number", 42);
    }

    @Test
    void handlesInvalidJson() {
        JsonParser parser = JsonParserFactory.getJsonParser();

        assertThatThrownBy(() -> parser.parseMap("{invalid}"))
            .isInstanceOf(JsonParseException.class);
    }
}

Type Conversion Notes

Different parsers handle numeric types differently:

Jackson:

  • Integers as Integer or Long
  • Decimals as Double
  • Booleans as Boolean

Gson:

  • All numbers as Double
  • Booleans as Boolean

Basic:

  • Numbers as Number (Integer, Long, or Double)
  • Booleans as Boolean

Always cast appropriately:

Map<String, Object> data = parser.parseMap(json);

// Safe casting
Object numberObj = data.get("count");
int count;
if (numberObj instanceof Number) {
    count = ((Number) numberObj).intValue();
} else {
    count = Integer.parseInt(numberObj.toString());
}

Performance Considerations

Parser Selection Priority:

  1. Jackson: Fastest, most features, best for production
  2. Gson: Good performance, lighter than Jackson
  3. Basic: Slowest, use only when no dependencies allowed

Common Patterns

Pattern 1: Configuration-Based JSON Parser with Fallback

Production-ready JSON parsing with automatic parser selection and comprehensive error handling:

package com.example.json;

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
import org.springframework.boot.json.JacksonJsonParser;
import org.springframework.boot.json.BasicJsonParser;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Production-ready JSON parser with caching and fallback support.
 * Automatically selects best available parser and caches parsed results.
 */
@Component
public class RobustJsonParser {

    private final JsonParser primaryParser;
    private final JsonParser fallbackParser;
    private final Map<String, Map<String, Object>> mapCache;
    private final Map<String, List<Object>> listCache;
    private final int maxCacheSize;

    public RobustJsonParser() {
        this(1000);
    }

    public RobustJsonParser(int maxCacheSize) {
        // Try to use Jackson, fallback to Basic
        this.primaryParser = JsonParserFactory.getJsonParser();
        this.fallbackParser = new BasicJsonParser();
        this.mapCache = new ConcurrentHashMap<>();
        this.listCache = new ConcurrentHashMap<>();
        this.maxCacheSize = maxCacheSize;
    }

    /**
     * Parse JSON string to Map with caching and fallback.
     *
     * @param json the JSON string
     * @return parsed map
     * @throws JsonParseException if parsing fails with all parsers
     */
    public Map<String, Object> parseMap(String json) {
        if (json == null || json.trim().isEmpty()) {
            throw new IllegalArgumentException("JSON string cannot be null or empty");
        }

        // Check cache first
        String cacheKey = generateCacheKey(json);
        Map<String, Object> cached = mapCache.get(cacheKey);
        if (cached != null) {
            return cached;
        }

        try {
            Map<String, Object> result = primaryParser.parseMap(json);
            cacheResult(cacheKey, result);
            return result;
        } catch (JsonParseException e) {
            // Try fallback parser
            try {
                Map<String, Object> result = fallbackParser.parseMap(json);
                cacheResult(cacheKey, result);
                return result;
            } catch (JsonParseException fallbackException) {
                throw new JsonParseException(
                    "Failed to parse JSON with all available parsers. " +
                    "Primary: " + e.getMessage() + ", " +
                    "Fallback: " + fallbackException.getMessage(),
                    e
                );
            }
        }
    }

    /**
     * Parse JSON string to List with caching and fallback.
     *
     * @param json the JSON string
     * @return parsed list
     * @throws JsonParseException if parsing fails with all parsers
     */
    public List<Object> parseList(String json) {
        if (json == null || json.trim().isEmpty()) {
            throw new IllegalArgumentException("JSON string cannot be null or empty");
        }

        // Check cache first
        String cacheKey = generateCacheKey(json);
        List<Object> cached = listCache.get(cacheKey);
        if (cached != null) {
            return cached;
        }

        try {
            List<Object> result = primaryParser.parseList(json);
            cacheListResult(cacheKey, result);
            return result;
        } catch (JsonParseException e) {
            // Try fallback parser
            try {
                List<Object> result = fallbackParser.parseList(json);
                cacheListResult(cacheKey, result);
                return result;
            } catch (JsonParseException fallbackException) {
                throw new JsonParseException(
                    "Failed to parse JSON array with all available parsers",
                    e
                );
            }
        }
    }

    private void cacheResult(String key, Map<String, Object> result) {
        if (mapCache.size() < maxCacheSize) {
            mapCache.put(key, result);
        }
    }

    private void cacheListResult(String key, List<Object> result) {
        if (listCache.size() < maxCacheSize) {
            listCache.put(key, result);
        }
    }

    private String generateCacheKey(String json) {
        return String.valueOf(json.hashCode());
    }

    public void clearCache() {
        mapCache.clear();
        listCache.clear();
    }
}

Pattern 2: Type-Safe JSON Data Extractor

Extract and convert JSON values with type safety and validation:

package com.example.json;

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

/**
 * Type-safe JSON value extractor with comprehensive error handling.
 * Handles different parser implementations and their type variations.
 */
public class JsonValueExtractor {

    private final JsonParser parser;

    public JsonValueExtractor() {
        this.parser = JsonParserFactory.getJsonParser();
    }

    /**
     * Extract string value from JSON.
     *
     * @param json the JSON string
     * @param key the property key
     * @return the string value or empty if not found
     * @throws JsonParseException if JSON is invalid
     */
    public Optional<String> getString(String json, String key) {
        return getValue(json, key, obj -> {
            if (obj == null) return null;
            return obj.toString();
        });
    }

    /**
     * Extract integer value from JSON, handling different number types.
     *
     * @param json the JSON string
     * @param key the property key
     * @return the integer value or empty if not found
     * @throws JsonParseException if JSON is invalid
     * @throws NumberFormatException if value cannot be converted to integer
     */
    public Optional<Integer> getInteger(String json, String key) {
        return getValue(json, key, obj -> {
            if (obj == null) return null;
            if (obj instanceof Number) {
                return ((Number) obj).intValue();
            }
            return Integer.parseInt(obj.toString());
        });
    }

    /**
     * Extract long value from JSON, handling different number types.
     *
     * @param json the JSON string
     * @param key the property key
     * @return the long value or empty if not found
     * @throws JsonParseException if JSON is invalid
     * @throws NumberFormatException if value cannot be converted to long
     */
    public Optional<Long> getLong(String json, String key) {
        return getValue(json, key, obj -> {
            if (obj == null) return null;
            if (obj instanceof Number) {
                return ((Number) obj).longValue();
            }
            return Long.parseLong(obj.toString());
        });
    }

    /**
     * Extract double value from JSON, handling different number types.
     *
     * @param json the JSON string
     * @param key the property key
     * @return the double value or empty if not found
     * @throws JsonParseException if JSON is invalid
     * @throws NumberFormatException if value cannot be converted to double
     */
    public Optional<Double> getDouble(String json, String key) {
        return getValue(json, key, obj -> {
            if (obj == null) return null;
            if (obj instanceof Number) {
                return ((Number) obj).doubleValue();
            }
            return Double.parseDouble(obj.toString());
        });
    }

    /**
     * Extract boolean value from JSON.
     *
     * @param json the JSON string
     * @param key the property key
     * @return the boolean value or empty if not found
     * @throws JsonParseException if JSON is invalid
     */
    public Optional<Boolean> getBoolean(String json, String key) {
        return getValue(json, key, obj -> {
            if (obj == null) return null;
            if (obj instanceof Boolean) {
                return (Boolean) obj;
            }
            return Boolean.parseBoolean(obj.toString());
        });
    }

    /**
     * Extract nested map from JSON.
     *
     * @param json the JSON string
     * @param key the property key
     * @return the nested map or empty if not found
     * @throws JsonParseException if JSON is invalid
     * @throws ClassCastException if value is not a map
     */
    @SuppressWarnings("unchecked")
    public Optional<Map<String, Object>> getMap(String json, String key) {
        return getValue(json, key, obj -> {
            if (obj == null) return null;
            if (obj instanceof Map) {
                return (Map<String, Object>) obj;
            }
            throw new ClassCastException("Value at key '" + key + "' is not a Map");
        });
    }

    /**
     * Extract list from JSON.
     *
     * @param json the JSON string
     * @param key the property key
     * @return the list or empty if not found
     * @throws JsonParseException if JSON is invalid
     * @throws ClassCastException if value is not a list
     */
    @SuppressWarnings("unchecked")
    public Optional<List<Object>> getList(String json, String key) {
        return getValue(json, key, obj -> {
            if (obj == null) return null;
            if (obj instanceof List) {
                return (List<Object>) obj;
            }
            throw new ClassCastException("Value at key '" + key + "' is not a List");
        });
    }

    /**
     * Generic value extraction with custom converter.
     *
     * @param json the JSON string
     * @param key the property key
     * @param converter function to convert raw object to desired type
     * @return the converted value or empty if not found
     * @throws JsonParseException if JSON is invalid
     */
    private <T> Optional<T> getValue(String json, String key, Function<Object, T> converter) {
        Map<String, Object> map = parser.parseMap(json);
        Object value = map.get(key);

        if (value == null) {
            return Optional.empty();
        }

        try {
            T converted = converter.apply(value);
            return Optional.ofNullable(converted);
        } catch (Exception e) {
            throw new IllegalArgumentException(
                "Failed to convert value at key '" + key + "': " + e.getMessage(),
                e
            );
        }
    }

    /**
     * Extract value from nested path (e.g., "user.address.city").
     *
     * @param json the JSON string
     * @param path dot-separated path
     * @return the value or empty if not found
     * @throws JsonParseException if JSON is invalid
     */
    @SuppressWarnings("unchecked")
    public Optional<Object> getNestedValue(String json, String path) {
        Map<String, Object> current = parser.parseMap(json);
        String[] parts = path.split("\\.");

        for (int i = 0; i < parts.length - 1; i++) {
            Object next = current.get(parts[i]);
            if (!(next instanceof Map)) {
                return Optional.empty();
            }
            current = (Map<String, Object>) next;
        }

        return Optional.ofNullable(current.get(parts[parts.length - 1]));
    }
}

Pattern 3: Batch JSON Processing Pipeline

Process multiple JSON documents efficiently with streaming and error recovery:

package com.example.json;

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * Batch processor for handling multiple JSON documents with parallel processing.
 * Includes error recovery and progress tracking.
 */
public class JsonBatchProcessor implements AutoCloseable {

    private final JsonParser parser;
    private final ExecutorService executor;
    private final Consumer<ProcessingError> errorHandler;
    private final int batchSize;

    public JsonBatchProcessor(int threadCount, Consumer<ProcessingError> errorHandler) {
        this(threadCount, errorHandler, 100);
    }

    public JsonBatchProcessor(int threadCount, Consumer<ProcessingError> errorHandler, int batchSize) {
        this.parser = JsonParserFactory.getJsonParser();
        this.executor = Executors.newFixedThreadPool(threadCount);
        this.errorHandler = errorHandler;
        this.batchSize = batchSize;
    }

    /**
     * Process multiple JSON documents in parallel.
     *
     * @param jsonDocuments list of JSON strings to process
     * @param processor function to process each parsed document
     * @return list of processing results
     */
    public <T> List<T> processBatch(
            List<String> jsonDocuments,
            JsonDocumentProcessor<T> processor) {

        List<CompletableFuture<T>> futures = new ArrayList<>();

        for (int i = 0; i < jsonDocuments.size(); i++) {
            final int index = i;
            final String json = jsonDocuments.get(i);

            CompletableFuture<T> future = CompletableFuture.supplyAsync(() -> {
                try {
                    Map<String, Object> parsed = parser.parseMap(json);
                    return processor.process(parsed, index);
                } catch (JsonParseException e) {
                    errorHandler.accept(new ProcessingError(index, json, e));
                    return null;
                } catch (Exception e) {
                    errorHandler.accept(new ProcessingError(index, json, e));
                    return null;
                }
            }, executor);

            futures.add(future);
        }

        // Wait for all to complete and collect non-null results
        return futures.stream()
            .map(CompletableFuture::join)
            .filter(result -> result != null)
            .collect(Collectors.toList());
    }

    /**
     * Process JSON documents in batches for better memory management.
     *
     * @param jsonDocuments list of JSON strings to process
     * @param processor function to process each parsed document
     * @return list of processing results
     */
    public <T> List<T> processInBatches(
            List<String> jsonDocuments,
            JsonDocumentProcessor<T> processor) {

        List<T> allResults = new ArrayList<>();

        for (int i = 0; i < jsonDocuments.size(); i += batchSize) {
            int end = Math.min(i + batchSize, jsonDocuments.size());
            List<String> batch = jsonDocuments.subList(i, end);

            List<T> batchResults = processBatch(batch, processor);
            allResults.addAll(batchResults);

            // Optional: Log progress
            System.out.printf("Processed %d/%d documents%n", end, jsonDocuments.size());
        }

        return allResults;
    }

    /**
     * Process JSON array string into individual documents.
     *
     * @param jsonArray JSON array string
     * @param processor function to process each element
     * @return list of processing results
     */
    @SuppressWarnings("unchecked")
    public <T> List<T> processJsonArray(
            String jsonArray,
            JsonDocumentProcessor<T> processor) {

        List<Object> items = parser.parseList(jsonArray);
        List<String> jsonStrings = new ArrayList<>();

        // Convert each item back to JSON string for processing
        for (Object item : items) {
            if (item instanceof Map) {
                // Simple JSON reconstruction (for production use proper serializer)
                jsonStrings.add(reconstructJson((Map<String, Object>) item));
            }
        }

        return processBatch(jsonStrings, processor);
    }

    private String reconstructJson(Map<String, Object> map) {
        // Simplified JSON reconstruction - in production use Jackson/Gson
        StringBuilder sb = new StringBuilder("{");
        boolean first = true;
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (!first) sb.append(",");
            sb.append("\"").append(entry.getKey()).append("\":");
            if (entry.getValue() instanceof String) {
                sb.append("\"").append(entry.getValue()).append("\"");
            } else {
                sb.append(entry.getValue());
            }
            first = false;
        }
        sb.append("}");
        return sb.toString();
    }

    @Override
    public void close() {
        executor.shutdown();
    }

    @FunctionalInterface
    public interface JsonDocumentProcessor<T> {
        T process(Map<String, Object> document, int index) throws Exception;
    }

    public static class ProcessingError {
        public final int index;
        public final String json;
        public final Exception exception;

        public ProcessingError(int index, String json, Exception exception) {
            this.index = index;
            this.json = json;
            this.exception = exception;
        }

        @Override
        public String toString() {
            return String.format("Error at index %d: %s", index, exception.getMessage());
        }
    }
}

Pattern 4: JSON Schema Validator

Validate JSON structure and data types before processing:

package com.example.json;

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Validates JSON documents against expected schemas.
 * Provides detailed validation errors for debugging.
 */
public class JsonSchemaValidator {

    private final JsonParser parser;

    public JsonSchemaValidator() {
        this.parser = JsonParserFactory.getJsonParser();
    }

    /**
     * Validate JSON document against required fields.
     *
     * @param json the JSON string
     * @param requiredFields set of required field names
     * @return validation result
     */
    public ValidationResult validateRequiredFields(String json, Set<String> requiredFields) {
        List<String> errors = new ArrayList<>();

        try {
            Map<String, Object> map = parser.parseMap(json);

            for (String field : requiredFields) {
                if (!map.containsKey(field)) {
                    errors.add("Missing required field: " + field);
                } else if (map.get(field) == null) {
                    errors.add("Required field has null value: " + field);
                }
            }

            return new ValidationResult(errors.isEmpty(), errors);

        } catch (JsonParseException e) {
            errors.add("Invalid JSON: " + e.getMessage());
            return new ValidationResult(false, errors);
        }
    }

    /**
     * Validate JSON field types.
     *
     * @param json the JSON string
     * @param fieldTypes map of field name to expected type
     * @return validation result
     */
    public ValidationResult validateFieldTypes(String json, Map<String, Class<?>> fieldTypes) {
        List<String> errors = new ArrayList<>();

        try {
            Map<String, Object> map = parser.parseMap(json);

            for (Map.Entry<String, Class<?>> entry : fieldTypes.entrySet()) {
                String field = entry.getKey();
                Class<?> expectedType = entry.getValue();

                if (!map.containsKey(field)) {
                    continue; // Field not present, skip type check
                }

                Object value = map.get(field);
                if (value == null) {
                    continue; // Null values are allowed unless checked separately
                }

                if (!isCompatibleType(value, expectedType)) {
                    errors.add(String.format(
                        "Field '%s' has wrong type. Expected: %s, Actual: %s",
                        field,
                        expectedType.getSimpleName(),
                        value.getClass().getSimpleName()
                    ));
                }
            }

            return new ValidationResult(errors.isEmpty(), errors);

        } catch (JsonParseException e) {
            errors.add("Invalid JSON: " + e.getMessage());
            return new ValidationResult(false, errors);
        }
    }

    /**
     * Validate JSON with custom validator function.
     *
     * @param json the JSON string
     * @param customValidator custom validation function
     * @return validation result
     */
    public ValidationResult validateCustom(
            String json,
            java.util.function.Function<Map<String, Object>, List<String>> customValidator) {

        try {
            Map<String, Object> map = parser.parseMap(json);
            List<String> errors = customValidator.apply(map);
            return new ValidationResult(errors.isEmpty(), errors);

        } catch (JsonParseException e) {
            return new ValidationResult(false, List.of("Invalid JSON: " + e.getMessage()));
        }
    }

    /**
     * Comprehensive validation combining multiple checks.
     *
     * @param json the JSON string
     * @param requiredFields required field names
     * @param fieldTypes expected field types
     * @return validation result
     */
    public ValidationResult validateComprehensive(
            String json,
            Set<String> requiredFields,
            Map<String, Class<?>> fieldTypes) {

        List<String> allErrors = new ArrayList<>();

        ValidationResult requiredResult = validateRequiredFields(json, requiredFields);
        allErrors.addAll(requiredResult.errors);

        if (requiredResult.valid) {
            ValidationResult typeResult = validateFieldTypes(json, fieldTypes);
            allErrors.addAll(typeResult.errors);
        }

        return new ValidationResult(allErrors.isEmpty(), allErrors);
    }

    private boolean isCompatibleType(Object value, Class<?> expectedType) {
        // Handle Number types from different parsers
        if (expectedType == Integer.class || expectedType == int.class) {
            return value instanceof Number;
        }
        if (expectedType == Long.class || expectedType == long.class) {
            return value instanceof Number;
        }
        if (expectedType == Double.class || expectedType == double.class) {
            return value instanceof Number;
        }
        if (expectedType == Boolean.class || expectedType == boolean.class) {
            return value instanceof Boolean;
        }
        if (expectedType == String.class) {
            return value instanceof String;
        }
        if (expectedType == Map.class) {
            return value instanceof Map;
        }
        if (expectedType == List.class) {
            return value instanceof List;
        }

        return expectedType.isInstance(value);
    }

    public static class ValidationResult {
        public final boolean valid;
        public final List<String> errors;

        public ValidationResult(boolean valid, List<String> errors) {
            this.valid = valid;
            this.errors = errors;
        }

        public void throwIfInvalid() {
            if (!valid) {
                throw new IllegalArgumentException(
                    "JSON validation failed: " + String.join(", ", errors)
                );
            }
        }

        @Override
        public String toString() {
            if (valid) {
                return "Validation passed";
            }
            return "Validation failed: " + String.join("; ", errors);
        }
    }
}

Pattern 5: JSON Configuration Loader

Load and parse configuration files with environment variable substitution:

package com.example.json;

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.DefaultResourceLoader;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Loads JSON configuration files with support for environment variable substitution.
 * Handles classpath, file system, and URL resources.
 */
public class JsonConfigurationLoader {

    private final JsonParser parser;
    private final ResourceLoader resourceLoader;
    private final Pattern envVarPattern;

    public JsonConfigurationLoader() {
        this(new DefaultResourceLoader());
    }

    public JsonConfigurationLoader(ResourceLoader resourceLoader) {
        this.parser = JsonParserFactory.getJsonParser();
        this.resourceLoader = resourceLoader;
        this.envVarPattern = Pattern.compile("\\$\\{([^}]+)}");
    }

    /**
     * Load configuration from classpath, file, or URL.
     *
     * @param location resource location (e.g., "classpath:config.json", "file:/etc/app/config.json")
     * @return parsed configuration map
     * @throws IOException if resource cannot be read
     * @throws JsonParseException if JSON is invalid
     */
    public Map<String, Object> loadConfiguration(String location) throws IOException {
        Resource resource = resourceLoader.getResource(location);

        if (!resource.exists()) {
            throw new IOException("Configuration file not found: " + location);
        }

        String content = readResourceContent(resource);
        String substituted = substituteEnvironmentVariables(content);

        try {
            return parser.parseMap(substituted);
        } catch (JsonParseException e) {
            throw new JsonParseException(
                "Failed to parse configuration from " + location + ": " + e.getMessage(),
                e
            );
        }
    }

    /**
     * Load configuration with default fallback.
     *
     * @param location primary configuration location
     * @param defaultLocation fallback configuration location
     * @return parsed configuration map
     * @throws IOException if both locations fail
     */
    public Map<String, Object> loadConfigurationWithFallback(
            String location,
            String defaultLocation) throws IOException {

        try {
            return loadConfiguration(location);
        } catch (IOException e) {
            System.err.println("Primary config not found, using fallback: " + e.getMessage());
            return loadConfiguration(defaultLocation);
        }
    }

    /**
     * Load configuration with profile support.
     *
     * @param baseName base configuration name without extension
     * @param profile active profile (e.g., "dev", "prod")
     * @return merged configuration map
     * @throws IOException if configuration cannot be read
     */
    public Map<String, Object> loadConfigurationWithProfile(
            String baseName,
            String profile) throws IOException {

        // Load base configuration
        Map<String, Object> config = loadConfiguration(baseName + ".json");

        // Try to load profile-specific configuration
        try {
            String profileLocation = baseName + "-" + profile + ".json";
            Map<String, Object> profileConfig = loadConfiguration(profileLocation);

            // Merge profile config into base config (profile overrides base)
            config.putAll(profileConfig);
        } catch (IOException e) {
            // Profile-specific config is optional
            System.out.println("No profile-specific config found for: " + profile);
        }

        return config;
    }

    private String readResourceContent(Resource resource) throws IOException {
        try (InputStream is = resource.getInputStream()) {
            return new String(is.readAllBytes(), StandardCharsets.UTF_8);
        }
    }

    /**
     * Substitute environment variables in format ${VAR_NAME} or ${VAR_NAME:default}.
     *
     * @param content content with environment variable placeholders
     * @return content with substituted values
     */
    private String substituteEnvironmentVariables(String content) {
        Matcher matcher = envVarPattern.matcher(content);
        StringBuffer result = new StringBuffer();

        while (matcher.find()) {
            String varExpression = matcher.group(1);
            String replacement = resolveEnvironmentVariable(varExpression);
            matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
        }

        matcher.appendTail(result);
        return result.toString();
    }

    private String resolveEnvironmentVariable(String expression) {
        // Support format: VAR_NAME or VAR_NAME:defaultValue
        String[] parts = expression.split(":", 2);
        String varName = parts[0].trim();
        String defaultValue = parts.length > 1 ? parts[1].trim() : "";

        String value = System.getenv(varName);
        if (value == null) {
            value = System.getProperty(varName);
        }

        return (value != null) ? value : defaultValue;
    }

    /**
     * Validate configuration against schema.
     *
     * @param location configuration location
     * @param validator schema validator
     * @return validated configuration map
     * @throws IOException if resource cannot be read
     * @throws IllegalArgumentException if validation fails
     */
    public Map<String, Object> loadAndValidate(
            String location,
            JsonSchemaValidator validator,
            java.util.Set<String> requiredFields) throws IOException {

        Map<String, Object> config = loadConfiguration(location);

        // Convert map back to JSON string for validation
        // In production, use proper JSON serializer
        String jsonString = reconstructJsonFromMap(config);

        JsonSchemaValidator.ValidationResult result =
            validator.validateRequiredFields(jsonString, requiredFields);

        result.throwIfInvalid();

        return config;
    }

    private String reconstructJsonFromMap(Map<String, Object> map) {
        // Simplified reconstruction - use Jackson/Gson in production
        StringBuilder sb = new StringBuilder("{");
        boolean first = true;
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (!first) sb.append(",");
            sb.append("\"").append(entry.getKey()).append("\":");
            appendValue(sb, entry.getValue());
            first = false;
        }
        sb.append("}");
        return sb.toString();
    }

    @SuppressWarnings("unchecked")
    private void appendValue(StringBuilder sb, Object value) {
        if (value == null) {
            sb.append("null");
        } else if (value instanceof String) {
            sb.append("\"").append(value).append("\"");
        } else if (value instanceof Map) {
            sb.append(reconstructJsonFromMap((Map<String, Object>) value));
        } else if (value instanceof List) {
            sb.append("[");
            List<?> list = (List<?>) value;
            for (int i = 0; i < list.size(); i++) {
                if (i > 0) sb.append(",");
                appendValue(sb, list.get(i));
            }
            sb.append("]");
        } else {
            sb.append(value);
        }
    }
}

Common Pitfalls

1. Not Handling Different Number Types

Problem: Different parsers return different number types, causing ClassCastException

Error:

java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer

Solution:

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;

import java.util.Map;

public class SafeNumberExtraction {

    private final JsonParser parser = JsonParserFactory.getJsonParser();

    public int extractIntSafely(String json, String key) {
        Map<String, Object> map = parser.parseMap(json);
        Object value = map.get(key);

        // Wrong - causes ClassCastException with Gson
        // Integer num = (Integer) value;

        // Correct - handles all parser types
        if (value instanceof Number) {
            return ((Number) value).intValue();
        }

        throw new IllegalArgumentException("Value at key '" + key + "' is not a number");
    }
}

Rationale: Jackson returns Integer/Long/Double based on value, Gson always returns Double for decimals, and BasicJsonParser returns Number. Always cast to Number first, then convert to specific type.

2. Ignoring Null Values in Maps

Problem: Attempting to cast null values from parsed JSON

Error:

java.lang.NullPointerException: Cannot invoke method on null object

Solution:

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;

import java.util.Map;

public class SafeValueAccess {

    private final JsonParser parser = JsonParserFactory.getJsonParser();

    public String getValueSafely(String json, String key) {
        Map<String, Object> map = parser.parseMap(json);

        // Wrong - throws NPE if key doesn't exist or value is null
        // return map.get(key).toString();

        // Correct - check for existence and null
        if (!map.containsKey(key)) {
            return "KEY_NOT_FOUND";
        }

        Object value = map.get(key);
        return (value != null) ? value.toString() : "NULL_VALUE";
    }
}

Rationale: JSON null values are represented as Java null in the parsed map. Always check for null before accessing or converting values to prevent NullPointerException.

3. Not Closing Resources in Try-Catch

Problem: JsonParseException is unchecked, leading to improper resource cleanup

Error: Resource leaks when parsing fails in the middle of resource handling

Solution:

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Map;

public class SafeFileJsonParsing {

    private final JsonParser parser = JsonParserFactory.getJsonParser();

    public Map<String, Object> parseJsonFile(String filePath) throws IOException {
        // Wrong - resource leak if JsonParseException occurs
        // BufferedReader reader = new BufferedReader(new FileReader(filePath));
        // String json = reader.readLine();
        // return parser.parseMap(json);

        // Correct - use try-with-resources
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            StringBuilder json = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                json.append(line);
            }

            return parser.parseMap(json.toString());

        } catch (JsonParseException e) {
            throw new IOException("Failed to parse JSON from file: " + filePath, e);
        }
    }
}

Rationale: JsonParseException is a RuntimeException and can be thrown at any time. Use try-with-resources to ensure proper cleanup even when parsing fails.

4. Assuming Parser Implementation

Problem: Hardcoding parser selection instead of using factory

Error:

java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper

Solution:

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JacksonJsonParser;

public class ParserSelection {

    // Wrong - fails if Jackson not on classpath
    // private final JsonParser parser = new JacksonJsonParser();

    // Correct - automatically selects best available parser
    private final JsonParser parser = JsonParserFactory.getJsonParser();

    // Also correct - explicitly specify with fallback handling
    private JsonParser createParserWithFallback() {
        try {
            return new JacksonJsonParser();
        } catch (NoClassDefFoundError e) {
            System.out.println("Jackson not available, using factory selection");
            return JsonParserFactory.getJsonParser();
        }
    }
}

Rationale: JsonParserFactory automatically detects available parsers and selects the best one. Hardcoding parser types causes ClassNotFoundException when dependencies are missing.

5. Not Validating JSON Before Parsing

Problem: Parsing invalid or malformed JSON without validation

Error:

org.springframework.boot.json.JsonParseException: Cannot parse JSON

Solution:

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;

import java.util.Map;

public class ValidatedJsonParsing {

    private final JsonParser parser = JsonParserFactory.getJsonParser();

    public Map<String, Object> parseWithValidation(String json) {
        // Validate input
        if (json == null || json.trim().isEmpty()) {
            throw new IllegalArgumentException("JSON string cannot be null or empty");
        }

        // Check basic JSON structure
        String trimmed = json.trim();
        if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
            throw new IllegalArgumentException("JSON must be an object (start with { and end with })");
        }

        // Parse with error handling
        try {
            return parser.parseMap(json);
        } catch (JsonParseException e) {
            // Provide context in error message
            String preview = json.length() > 100 ? json.substring(0, 100) + "..." : json;
            throw new JsonParseException(
                "Failed to parse JSON. Preview: " + preview + ". Error: " + e.getMessage(),
                e
            );
        }
    }
}

Rationale: Pre-validation catches common issues early and provides better error messages. Always validate input before attempting expensive parsing operations.

6. Unsafe Type Casting of Nested Objects

Problem: Directly casting nested objects without type checking

Error:

java.lang.ClassCastException: java.lang.String cannot be cast to java.util.Map

Solution:

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;

import java.util.Map;

public class SafeNestedAccess {

    private final JsonParser parser = JsonParserFactory.getJsonParser();

    @SuppressWarnings("unchecked")
    public String getNestedValueSafely(String json, String parentKey, String childKey) {
        Map<String, Object> map = parser.parseMap(json);

        // Wrong - crashes if parent is not a Map
        // Map<String, Object> nested = (Map<String, Object>) map.get(parentKey);
        // return (String) nested.get(childKey);

        // Correct - verify type before casting
        Object parentObj = map.get(parentKey);
        if (!(parentObj instanceof Map)) {
            throw new IllegalArgumentException(
                "Expected Map at key '" + parentKey + "' but found: " +
                (parentObj != null ? parentObj.getClass().getSimpleName() : "null")
            );
        }

        Map<String, Object> nested = (Map<String, Object>) parentObj;
        Object value = nested.get(childKey);

        return (value != null) ? value.toString() : null;
    }
}

Rationale: JSON structure can vary, and nested objects might be missing or of unexpected types. Always verify types before casting to prevent ClassCastException.

7. Memory Issues with Large JSON

Problem: Parsing very large JSON documents into memory all at once

Error:

java.lang.OutOfMemoryError: Java heap space

Solution:

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class StreamingJsonParsing {

    private final JsonParser parser = JsonParserFactory.getJsonParser();

    // Wrong - loads entire file into memory
    public void processLargeFileWrong(String filePath) throws Exception {
        String entireJson = new String(java.nio.file.Files.readAllBytes(
            java.nio.file.Paths.get(filePath)
        ));
        List<Object> items = parser.parseList(entireJson); // OOM if file is huge
    }

    // Correct - process line by line if JSON Lines format
    public void processJsonLines(String filePath,
                                  java.util.function.Consumer<Map<String, Object>> processor)
            throws Exception {
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            int lineNumber = 0;

            while ((line = reader.readLine()) != null) {
                lineNumber++;
                try {
                    if (!line.trim().isEmpty()) {
                        Map<String, Object> item = parser.parseMap(line);
                        processor.accept(item);
                    }
                } catch (Exception e) {
                    System.err.println("Error parsing line " + lineNumber + ": " + e.getMessage());
                }
            }
        }
    }
}

Rationale: Spring Boot's JsonParser loads entire JSON into memory. For large files, use streaming parsers like Jackson's JsonParser or process JSON Lines format. Consider limiting document size or implementing pagination.

8. Not Handling Empty Arrays and Objects

Problem: Assuming arrays and objects always contain elements

Error:

java.util.NoSuchElementException: No value present
java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0

Solution:

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;

import java.util.List;
import java.util.Map;

public class SafeCollectionAccess {

    private final JsonParser parser = JsonParserFactory.getJsonParser();

    public Object getFirstArrayElement(String jsonArray) {
        List<Object> list = parser.parseList(jsonArray);

        // Wrong - crashes on empty array
        // return list.get(0);

        // Correct - check size first
        if (list.isEmpty()) {
            return null; // or throw meaningful exception
        }

        return list.get(0);
    }

    public boolean hasAnyKeys(String json) {
        Map<String, Object> map = parser.parseMap(json);

        // Correct - handle empty objects
        if (map.isEmpty()) {
            System.out.println("JSON object is empty");
            return false;
        }

        return true;
    }
}

Rationale: Valid JSON can contain empty arrays [] and empty objects {}. Always check collection size before accessing elements to prevent IndexOutOfBoundsException.

9. Incorrect Error Recovery

Problem: Catching JsonParseException but continuing with null or empty results

Error: Silent failures leading to incorrect application behavior

Solution:

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;

import java.util.Map;
import java.util.Collections;

public class ProperErrorRecovery {

    private final JsonParser parser = JsonParserFactory.getJsonParser();

    // Wrong - swallows errors and returns empty map
    public Map<String, Object> parseJsonWrong(String json) {
        try {
            return parser.parseMap(json);
        } catch (JsonParseException e) {
            e.printStackTrace();
            return Collections.emptyMap(); // Hides the problem!
        }
    }

    // Correct - fail fast with clear error
    public Map<String, Object> parseJsonCorrect(String json) {
        try {
            return parser.parseMap(json);
        } catch (JsonParseException e) {
            throw new IllegalArgumentException(
                "Failed to parse JSON configuration. " +
                "Please verify the JSON syntax. Error: " + e.getMessage(),
                e
            );
        }
    }

    // Also correct - provide default only if appropriate
    public Map<String, Object> parseJsonWithDefault(String json,
                                                      Map<String, Object> defaultConfig) {
        try {
            return parser.parseMap(json);
        } catch (JsonParseException e) {
            System.err.println("JSON parsing failed, using default config: " + e.getMessage());
            return defaultConfig;
        }
    }
}

Rationale: Silently recovering from parse errors by returning empty collections hides problems and causes hard-to-debug issues. Either fail fast with clear errors or provide sensible defaults with logging.

10. Unicode and Special Character Issues

Problem: Not handling Unicode characters and escape sequences properly

Error:

org.springframework.boot.json.JsonParseException: Invalid escape sequence
Garbled text for non-ASCII characters

Solution:

import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;

public class UnicodeJsonParsing {

    private final JsonParser parser = JsonParserFactory.getJsonParser();

    // Wrong - uses platform default encoding
    public void readJsonFileWrong(String filePath) throws IOException {
        try (FileInputStream fis = new FileInputStream(filePath)) {
            // May use wrong encoding (e.g., Windows-1252 on Windows)
            Reader reader = new InputStreamReader(fis);
            // ... rest of code
        }
    }

    // Correct - explicitly use UTF-8
    public String readJsonFileCorrect(String filePath) throws IOException {
        try (FileInputStream fis = new FileInputStream(filePath);
             InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) {

            StringBuilder json = new StringBuilder();
            char[] buffer = new char[8192];
            int charsRead;

            while ((charsRead = isr.read(buffer)) != -1) {
                json.append(buffer, 0, charsRead);
            }

            return json.toString();
        }
    }

    // Handle JSON with escaped Unicode
    public void demonstrateUnicodeHandling() {
        // JSON with Unicode escape sequences
        String json = "{\"message\":\"Hello \\u4E16\\u754C\"}"; // "Hello 世界"

        Map<String, Object> map = parser.parseMap(json);

        // Parser automatically handles Unicode escapes
        String message = (String) map.get("message");
        System.out.println(message); // Prints: Hello 世界
    }
}

Rationale: JSON is UTF-8 by default. Always use StandardCharsets.UTF_8 when reading JSON files to prevent encoding issues. The parsers handle Unicode escape sequences automatically.

Thread Safety

All parser implementations are thread-safe and can be safely shared across threads.

Best Practices

  1. Use Factory: Let JsonParserFactory select best parser
  2. Type Safety: Always validate types when casting parsed values
  3. Error Handling: Always wrap parse calls in try-catch
  4. Null Checks: Verify keys exist before accessing values
  5. Prefer Jackson: Include Jackson dependency for best performance

Import Statements

// Core interfaces
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;

// Specific implementations
import org.springframework.boot.json.JacksonJsonParser;
import org.springframework.boot.json.GsonJsonParser;
import org.springframework.boot.json.BasicJsonParser;

// Java types
import java.util.Map;
import java.util.List;