diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bec247b..dde6c74 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,27 +8,25 @@ on:
branches:
- main
workflow_dispatch:
- branches:
- - main
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
- java: [ '11' ]
+ java: [ '21' ]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK ${{ matrix.java }}
- uses: actions/setup-java@v2
+ uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
- distribution: 'adopt'
+ distribution: 'temurin'
- name: Print Java version
run: java -version
- - name: Gradle wrapper validation
- uses: gradle/wrapper-validation-action@v1
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v4
- name: Run tests
- run: ./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --stacktrace clean build
+ run: ./gradlew --no-daemon --stacktrace clean build
diff --git a/README.md b/README.md
index 7598a74..a62173f 100644
--- a/README.md
+++ b/README.md
@@ -121,7 +121,7 @@ Add the following to your `pom.xml`:
com.arakelianjson-filter
- 4.0.1
+ 5.0.0compile
```
@@ -136,7 +136,7 @@ repositories {
}
dependencies {
- compile 'com.arakelian:json-filter:4.0.1'
+ compile 'com.arakelian:json-filter:5.0.0'
}
```
diff --git a/build.gradle b/build.gradle
index 278fd50..aba66b0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,47 +2,45 @@
plugins {
id 'java-library'
- id 'maven-publish'
- id 'signing'
+ id 'java-test-fixtures'
id 'eclipse'
id 'idea'
// keep dependencies up-to-date!
- id 'com.github.ben-manes.versions' version '0.46.0'
-
- // useful for creating immutable java beans
- id 'org.inferred.processors' version '3.7.0'
+ id 'com.github.ben-manes.versions' version '0.53.0'
// to ensure clean code
- id "net.ltgt.errorprone" version "3.1.0"
+ id "net.ltgt.errorprone" version "5.0.0"
// for deployment to Maven Central
- id "io.codearte.nexus-staging" version "0.30.0"
+ id "com.vanniktech.maven.publish" version "0.36.0"
}
group = 'com.arakelian'
-version = '4.0.1'
+version = '5.0.0'
apply from: "core.gradle"
-wrapper {
- gradleVersion = '8.1.1'
+wrapper {
+ gradleVersion = '9.3.1'
}
-publishing.publications.mavenJava {
+mavenPublishing {
+ coordinates(project.group.toString(), project.name, project.version.toString())
+
pom {
name = "Json Filter"
description = "High-speed stream-based filtering of arbitrarily large JSON documents."
url = "https://github.com/arakelian/json-filter"
-
+
licenses {
license {
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
-
+
developers {
developer {
id = 'arakelian'
@@ -50,7 +48,7 @@ publishing.publications.mavenJava {
email = 'greg@arakelian.com'
}
}
-
+
scm {
connection = 'scm:git:https://github.com/arakelian/json-filter.git'
developerConnection = 'scm:git:git@github.com:arakelian/json-filter.git'
@@ -60,28 +58,29 @@ publishing.publications.mavenJava {
}
dependencies {
- processor 'org.immutables:value:2.9.3'
+ annotationProcessor 'org.immutables:value:2.10.1'
// annotations
- api 'org.immutables:value-annotations:2.9.3'
+ api 'org.immutables:value-annotations:2.10.1'
// for date utils
- api 'com.arakelian:more-commons:4.0.1'
-
+ api 'com.arakelian:more-commons:5.1.0'
+
// configure errorprone version
- errorprone 'com.google.errorprone:error_prone_core:2.18.0'
+ errorprone 'com.google.errorprone:error_prone_core:2.36.0'
- // we shadow Guava due to frequent downstream compatibility issues
- api 'com.google.guava:guava:31.1-jre'
+ // we use Guava directly
+ api 'com.google.guava:guava:33.4.0-jre'
// logging
- testImplementation 'org.apache.logging.log4j:log4j-api:2.20.0'
- testImplementation 'org.apache.logging.log4j:log4j-core:2.20.0'
- testImplementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.20.0'
- testImplementation 'org.slf4j:jcl-over-slf4j:2.0.7'
- testImplementation 'org.slf4j:jul-to-slf4j:2.0.7'
- api 'org.slf4j:slf4j-api:2.0.7'
+ testImplementation 'org.apache.logging.log4j:log4j-api:2.24.3'
+ testImplementation 'org.apache.logging.log4j:log4j-core:2.24.3'
+ testImplementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3'
+ testImplementation 'org.slf4j:jcl-over-slf4j:2.0.16'
+ testImplementation 'org.slf4j:jul-to-slf4j:2.0.16'
+ api 'org.slf4j:slf4j-api:2.0.16'
// for unit testing
- testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
diff --git a/core.gradle b/core.gradle
index a32e0fc..1eea844 100644
--- a/core.gradle
+++ b/core.gradle
@@ -1,14 +1,4 @@
-buildscript {
- repositories {
- mavenCentral()
- }
- dependencies {
- classpath 'com.guardsquare:proguard-gradle:7.2.1' // The ProGuard Gradle plugin.
- }
-}
-
-
ext {
// useful macros, you can add your own
macros = [
@@ -16,18 +6,7 @@ ext {
'clean',
'classpath',
'build',
- 'minify',
- 'generatePomFileForInternalPublication', // needed to generate internal POM
- 'publishMavenJavaPublicationToMavenLocal' // real publication to Maven Local (~/.m2/repository)
- ],
- 'sonatype' : [
- 'generatePomFileForInternalPublication',
- 'publishMavenJavaPublicationToOssrhRepository',
- 'closeAndReleaseRepository'
- ],
- 'deploy' : [
- 'uploadArchives',
- 'closeAndReleaseRepository'
+ 'publishToMavenLocal'
],
'classpath' : [
'cleanEclipseClasspath',
@@ -37,14 +16,14 @@ ext {
'ideaModule'
],
]
-
+
// package patterns to exclude from Eclipse
excludeFromEclipse = []
}
// -------------------------------------------
-// REPOSITORIES / PUBLISHING
+// REPOSITORIES
// -------------------------------------------
repositories {
@@ -55,22 +34,7 @@ repositories {
mavenCentral()
}
-task sourcesJar(type: Jar) {
- archiveClassifier.set('sources')
- from sourceSets.main.allJava
-}
-
-task javadocJar(type: Jar, dependsOn: classes) {
- archiveClassifier.set('javadoc')
- from javadoc
-}
-
-task testsJar(type:Jar, dependsOn: testClasses) {
- archiveClassifier.set('tests')
- from sourceSets.test.output
-}
-
-test {
+tasks.named('test') {
// enable JUnit 5 tests
useJUnitPlatform()
}
@@ -80,108 +44,37 @@ test {
// JAVA COMPILER
// -------------------------------------------
-tasks.withType(JavaCompile) { task ->
- sourceCompatibility = 11
- targetCompatibility = 11
+tasks.withType(JavaCompile).configureEach { task ->
+ sourceCompatibility = 21
+ targetCompatibility = 21
// always UTF-8
options.encoding = 'UTF-8'
- // java 8 option which export names of constructor and method parameter names; no longer
- // have to declare parameter names with @JsonCreator
+ // export names of constructor and method parameter names
options.compilerArgs << "-parameters"
-
+
options.compilerArgs << '-Xlint:unchecked'
if (project.plugins.hasPlugin('net.ltgt.errorprone')) {
// Eclipse code formatting removes extraneous parenthesis which errorprone complains about
options.errorprone.disable 'OperatorPrecedence'
-
+
// we don't need to check return value always
options.errorprone.disable 'FutureReturnValueIgnored'
-
+
// generated code can have lots of bogus warnings
options.errorprone.disableWarningsInGeneratedCode = true
-
+
// bogus warning
options.errorprone.disable 'StringSplitter'
-
+
// ignore all generated source folders
options.errorprone.excludedPaths = '.*generated.*'
}
}
-// -------------------------------------------
-// SHADOW JAR
-// -------------------------------------------
-
-if(plugins.hasPlugin("com.github.johnrengelman.shadow")) {
- jar {
- archiveClassifier.set('original')
- }
-
- shadowJar {
- archiveClassifier.set('shadow')
- }
-
- sourceSets {
- // shadow configuration is added by Shadow plugin, but it's only configured for the main sourceset
- test.compileClasspath += configurations.shadow
- test.runtimeClasspath += configurations.shadow
- }
-}
-
-
-publishing {
- publications {
- mavenJava(MavenPublication) {
- from components.java
- }
- }
-}
-
-publishing {
- if(project.hasProperty('nexusUsername')) {
- repositories {
- maven {
- name = "ossrh"
- url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
- credentials {
- username = project.nexusUsername
- password = project.nexusPassword
- }
- }
- }
- }
-
- publications {
- mavenJava {
- artifact sourcesJar
- artifact javadocJar
- artifact testsJar
-
- versionMapping {
- usage('java-api') {
- fromResolutionOf('runtimeClasspath')
- }
- usage('java-runtime') {
- fromResolutionResult()
- }
- }
- }
- }
-}
-
-signing {
- sign publishing.publications.mavenJava
-}
-
-nexusStaging {
- repositoryDescription project.name
-}
-
-
// -------------------------------------------
// ECLIPSE
// -------------------------------------------
@@ -248,15 +141,15 @@ eclipse {
// final adjustments to .classpath file before it is saved
withXml { xml ->
def node = xml.asNode()
-
+
project_refs.unique(false).each { name ->
println "Creating Eclipse project dependency: " + name
node.appendNode('classpathentry', [ combineaccessrules: false, exported: true, kind: 'src', path: '/' + name ])
}
-
+
def apt = ['.apt_generated_test': 'bin/test',
'.apt_generated': 'bin/main']
-
+
apt.each { path, output ->
def atts = node.appendNode('classpathentry', [kind:'src', output: output, path: path]).appendNode('attributes')
atts.appendNode('attribute', [name: 'ignore_optional_problems', value: true])
@@ -273,12 +166,14 @@ eclipse {
// README
// -------------------------------------------
-task readme {
- ant.replaceregexp(match:'\\([0-9\\.]+)\\<\\/version\\>', replace:"${version}", flags:'g', byline:true) {
- fileset(dir: '.', includes: 'README.md')
- }
- ant.replaceregexp(match:'com\\.arakelian\\:' + project.name + ':([0-9\\.]+)', replace:"com.arakelian:${project.name}:${version}", flags:'g', byline:true) {
- fileset(dir: '.', includes: 'README.md')
+tasks.register('readme') {
+ doLast {
+ ant.replaceregexp(match:'\\([0-9\\.]+)\\<\\/version\\>', replace:"${version}", flags:'g', byline:true) {
+ fileset(dir: '.', includes: 'README.md')
+ }
+ ant.replaceregexp(match:'com\\.arakelian\\:' + project.name + ':([0-9\\.]+)', replace:"com.arakelian:${project.name}:${version}", flags:'g', byline:true) {
+ fileset(dir: '.', includes: 'README.md')
+ }
}
}
@@ -310,4 +205,3 @@ gradle.startParameter.taskNames.each { param ->
// replace command line arguments
gradle.startParameter.taskNames = newTasks.flatten()
-
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 37aef8d..78dfb56 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/src/main/java/com/arakelian/json/JsonFilter.java b/src/main/java/com/arakelian/json/JsonFilter.java
index f7b6a3b..70c7ae7 100644
--- a/src/main/java/com/arakelian/json/JsonFilter.java
+++ b/src/main/java/com/arakelian/json/JsonFilter.java
@@ -34,6 +34,10 @@
* where only a small number of fields may actually be used.
*/
public class JsonFilter {
+ /**
+ * Predicate that tests whether a given JSON field path should be included in the output based
+ * on the configured include and exclude patterns.
+ */
private static final class PathPredicate implements Predicate {
/** Path patterns which are included **/
private final Set includes;
@@ -165,6 +169,15 @@ private static boolean equals(final CharSequence lhs, final CharSequence rhs) {
return true;
}
+ /**
+ * Filters the given JSON string according to the specified options, returning a new JSON string
+ * with only the included fields.
+ *
+ * @param json the input JSON string
+ * @param options the filtering options specifying includes, excludes, and formatting
+ * @return the filtered JSON string, or the original string if no filtering is needed
+ * @throws IOException if the JSON is invalid or an I/O error occurs
+ */
public static CharSequence filter(final CharSequence json, final JsonFilterOptions options)
throws IOException {
if (json == null || json.length() == 0) {
@@ -298,6 +311,13 @@ public static CharSequence prettyifyQuietly(final CharSequence str) {
*/
private final JsonWriter writer;
+ /**
+ * Constructs a new {@code JsonFilter} with the given reader, writer, and options.
+ *
+ * @param reader the JSON reader to read input from
+ * @param writer the JSON writer to write filtered output to
+ * @param options the filtering options specifying includes, excludes, and formatting
+ */
public JsonFilter(final JsonReader reader, final JsonWriter writer, final JsonFilterOptions options) {
Preconditions.checkArgument(reader != null, "reader must be non-null");
Preconditions.checkArgument(writer != null, "writer must be non-null");
@@ -309,22 +329,49 @@ public JsonFilter(final JsonReader reader, final JsonWriter writer, final JsonFi
this.predicate = new PathPredicate(options.getIncludes(), options.getExcludes());
}
+ /**
+ * Returns the current field path being processed.
+ *
+ * @return the current field path
+ */
public CharSequence getCurrentPath() {
return currentPath;
}
+ /**
+ * Returns the current nesting depth within the JSON document.
+ *
+ * @return the current nesting depth
+ */
public final int getDepth() {
return depth;
}
+ /**
+ * Returns the filtering options used by this filter.
+ *
+ * @return the filtering options
+ */
public final JsonFilterOptions getOptions() {
return options;
}
+ /**
+ * Returns the JSON writer used by this filter.
+ *
+ * @return the JSON writer
+ */
public final JsonWriter getWriter() {
return writer;
}
+ /**
+ * Processes the input JSON, applying the configured filtering rules and writing the result to
+ * the output writer.
+ *
+ * @return this filter instance for chaining
+ * @throws IOException if the JSON is invalid or an I/O error occurs
+ */
public JsonFilter process() throws IOException {
depth = 0;
JsonToken token = reader.nextEvent();
diff --git a/src/main/java/com/arakelian/json/JsonFilterCallback.java b/src/main/java/com/arakelian/json/JsonFilterCallback.java
index 94083c7..93ffb39 100644
--- a/src/main/java/com/arakelian/json/JsonFilterCallback.java
+++ b/src/main/java/com/arakelian/json/JsonFilterCallback.java
@@ -19,11 +19,27 @@
import java.io.IOException;
+/**
+ * Callback interface for receiving notifications during JSON filtering. Implementations can inject
+ * custom logic at the start and end of JSON objects.
+ */
public interface JsonFilterCallback {
+ /**
+ * Called immediately after the start of a JSON object is written to the output.
+ *
+ * @param filter the filter currently processing the JSON
+ * @throws IOException if an I/O error occurs
+ */
@SuppressWarnings("unused")
public default void afterStartObject(final JsonFilter filter) throws IOException {
}
+ /**
+ * Called immediately before the end of a JSON object is written to the output.
+ *
+ * @param filter the filter currently processing the JSON
+ * @throws IOException if an I/O error occurs
+ */
@SuppressWarnings("unused")
public default void beforeEndObject(final JsonFilter filter) throws IOException {
}
diff --git a/src/main/java/com/arakelian/json/JsonFilterOptions.java b/src/main/java/com/arakelian/json/JsonFilterOptions.java
index 0def746..41cc157 100644
--- a/src/main/java/com/arakelian/json/JsonFilterOptions.java
+++ b/src/main/java/com/arakelian/json/JsonFilterOptions.java
@@ -22,33 +22,77 @@
import org.immutables.value.Value;
+/**
+ * Immutable configuration options for {@link JsonFilter}, specifying which fields to include or
+ * exclude, whether to pretty-print the output, and an optional callback for custom processing.
+ */
@Value.Immutable(copy = false)
public abstract class JsonFilterOptions {
+ /**
+ * Returns the optional callback for custom processing during filtering.
+ *
+ * @return the callback, or {@code null} if none
+ */
@Nullable
public abstract JsonFilterCallback getCallback();
+ /**
+ * Returns the set of field paths to exclude from the output.
+ *
+ * @return the set of excluded paths, or {@code null} if none
+ */
@Nullable
public abstract Set getExcludes();
+ /**
+ * Returns the set of field paths to include in the output.
+ *
+ * @return the set of included paths, or {@code null} if none
+ */
@Nullable
public abstract Set getIncludes();
+ /**
+ * Returns an optional flag indicating whether the output should be pretty-printed.
+ *
+ * @return an {@link Optional} containing the pretty-print flag
+ */
public abstract Optional getPretty();
+ /**
+ * Returns {@code true} if a callback has been configured.
+ *
+ * @return {@code true} if a callback is set
+ */
public final boolean hasCallback() {
return getCallback() != null;
}
+ /**
+ * Returns {@code true} if any exclude paths have been configured.
+ *
+ * @return {@code true} if excludes are set
+ */
public final boolean hasExcludes() {
final Set excludes = getExcludes();
return excludes != null && excludes.size() != 0;
}
+ /**
+ * Returns {@code true} if any include paths have been configured.
+ *
+ * @return {@code true} if includes are set
+ */
public final boolean hasIncludes() {
final Set includes = getIncludes();
return includes != null && includes.size() != 0;
}
+ /**
+ * Returns {@code true} if no includes, excludes, or callback have been configured.
+ *
+ * @return {@code true} if no filtering options are set
+ */
public final boolean isEmpty() {
return !hasIncludes() && !hasExcludes() && !hasCallback();
}
diff --git a/src/main/java/com/arakelian/json/JsonReader.java b/src/main/java/com/arakelian/json/JsonReader.java
index 0e15234..800609d 100644
--- a/src/main/java/com/arakelian/json/JsonReader.java
+++ b/src/main/java/com/arakelian/json/JsonReader.java
@@ -20,13 +20,29 @@
import java.io.IOException;
import java.io.Reader;
+/**
+ * High-performance streaming JSON reader that minimizes memory allocations by operating directly on
+ * character buffers. Supports reading from {@link CharSequence}, {@link Reader}, or raw character
+ * arrays.
+ */
public final class JsonReader {
+ /**
+ * Exception thrown when an error is encountered while parsing JSON input.
+ */
public static class JsonParseException extends IOException {
+ /**
+ * Constructs a new parse exception with the given message.
+ *
+ * @param msg the detail message
+ */
public JsonParseException(final String msg) {
super(msg);
}
}
+ /**
+ * Enumeration of JSON token types that can be encountered during parsing.
+ */
public enum JsonToken {
// Event indicating a JSON string value, including member names of objects
STRING,
@@ -146,6 +162,13 @@ enum ParserState {
/** current state while reading a number **/
private int numberState;
+ /**
+ * Constructs a reader that reads from the given character array.
+ *
+ * @param data the character array containing JSON
+ * @param start the start index (inclusive)
+ * @param end the end index (exclusive)
+ */
public JsonReader(final char[] data, final int start, final int end) {
this.in = null;
this.buf = data;
@@ -153,21 +176,44 @@ public JsonReader(final char[] data, final int start, final int end) {
this.end = end;
}
+ /**
+ * Constructs a reader that reads from the given {@link Reader} with a default buffer size.
+ *
+ * @param in the reader to obtain JSON data from
+ */
public JsonReader(final Reader in) {
// 8192 matches the default buffer size of a BufferedReader so double
// buffering of the data is avoided.
this(in, new char[8192]);
}
+ /**
+ * Constructs a reader that reads from the given {@link Reader} using the specified buffer.
+ *
+ * @param in the reader to obtain JSON data from
+ * @param buffer the buffer to use for reading
+ */
public JsonReader(final Reader in, final char[] buffer) {
this.in = in;
this.buf = buffer;
}
+ /**
+ * Constructs a reader that reads from the given {@link CharSequence}.
+ *
+ * @param data the character sequence containing JSON
+ */
public JsonReader(final CharSequence data) {
this(data, 0, data.length());
}
+ /**
+ * Constructs a reader that reads from a subsequence of the given {@link CharSequence}.
+ *
+ * @param data the character sequence containing JSON
+ * @param start the start index (inclusive)
+ * @param end the end index (exclusive)
+ */
public JsonReader(final CharSequence data, final int start, final int end) {
this.in = null;
this.start = start;
@@ -453,6 +499,11 @@ public CharSequence getNumberChars() throws IOException {
}
}
+ /**
+ * Returns the current position in the input stream.
+ *
+ * @return the current position
+ */
public long getPosition() {
return gpos + start;
}
@@ -521,6 +572,11 @@ private final boolean isWhitespace(final int ch) {
return Character.isWhitespace(ch) || ch == 0x00a0;
}
+ /**
+ * Returns the most recently read token.
+ *
+ * @return the last token read
+ */
public JsonToken lastEvent() {
return event;
}
@@ -996,11 +1052,20 @@ public String toString() {
return "start=" + start + ",end=" + end + ",state=" + state + "valstate=" + valueState;
}
+ /**
+ * Returns {@code true} if the last string read was an object member name (key).
+ *
+ * @return {@code true} if the last string was a key
+ */
public boolean wasKey() {
return state == ParserState.DID_MEMNAME;
}
}
+/**
+ * A no-op character array that discards all writes. Used internally by {@link JsonReader} to skip
+ * over values without allocating memory.
+ */
class NullCharArr extends SimpleCharArr {
public NullCharArr() {
super(new char[1], 0, 0);
@@ -1028,6 +1093,10 @@ public void write(final char b[], final int off, final int len) {
}
}
+/**
+ * A simple, resizable character array that implements {@link CharSequence}. Used internally by
+ * {@link JsonReader} as a lightweight buffer to avoid unnecessary {@link String} allocations.
+ */
class SimpleCharArr implements CharSequence {
protected char[] buf;
protected int start;
diff --git a/src/main/java/com/arakelian/json/JsonWriter.java b/src/main/java/com/arakelian/json/JsonWriter.java
index 27b829c..39ecac0 100644
--- a/src/main/java/com/arakelian/json/JsonWriter.java
+++ b/src/main/java/com/arakelian/json/JsonWriter.java
@@ -39,7 +39,11 @@
import com.google.common.io.BaseEncoding;
/**
- * Fast JSON writer
+ * High-performance JSON writer that serializes Java objects to JSON with support for pretty-printing
+ * and options to skip null or empty values. Operates on any {@link Writer} implementation and
+ * minimizes intermediate {@link String} allocations.
+ *
+ * @param the type of {@link Writer} to write JSON output to
*/
public class JsonWriter implements Closeable {
private static enum Container {
@@ -365,10 +369,18 @@ public static String toString(final Object value, final boolean pretty) throws I
/** True to skip empty values **/
private boolean skipEmpty = false;
+ /**
+ * Constructs a new {@code JsonWriter} with no underlying writer set.
+ */
public JsonWriter() {
reset();
}
+ /**
+ * Constructs a new {@code JsonWriter} that writes to the given writer.
+ *
+ * @param writer the writer to output JSON to
+ */
public JsonWriter(final W writer) {
setWriter(writer);
reset();
@@ -486,6 +498,13 @@ private void internalWriteUnescapedString(final CharSequence csq) throws IOExcep
}
}
+ /**
+ * Returns {@code true} if the given value is considered empty. Null values, empty strings, empty
+ * collections, empty maps, and non-finite floating point values are considered empty.
+ *
+ * @param value the value to test
+ * @return {@code true} if the value is empty
+ */
public final boolean isEmpty(final Object value) {
if (value == null) {
return true;
@@ -610,6 +629,13 @@ public final JsonWriter withWriter(final W writer) {
return this;
}
+ /**
+ * Writes the given byte array as a Base64-encoded JSON string value.
+ *
+ * @param data the byte array to encode, or {@code null} to write a JSON null
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeBase64String(final byte[] data) throws IOException {
if (data == null) {
return writeNull();
@@ -625,6 +651,14 @@ public final JsonWriter writeBase64String(final byte[] data) throws IOExcepti
return this;
}
+ /**
+ * Writes the given {@link BigDecimal} as a JSON number value using its plain string
+ * representation.
+ *
+ * @param val the value to write, or {@code null} to write a JSON null
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public JsonWriter writeBigDecimal(final BigDecimal val) throws IOException {
if (val == null) {
return writeNull();
@@ -633,11 +667,26 @@ public JsonWriter writeBigDecimal(final BigDecimal val) throws IOException {
return this;
}
+ /**
+ * Writes the given boolean as a JSON boolean value.
+ *
+ * @param val the boolean value
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeBoolean(final boolean val) throws IOException {
writeChars(val ? "true" : "false");
return this;
}
+ /**
+ * Writes the given {@link Boolean} as a JSON boolean value, or a JSON null if the value is
+ * {@code null}.
+ *
+ * @param val the boolean value, or {@code null} to write a JSON null
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeBoolean(final Boolean val) throws IOException {
if (val == null) {
return writeNull();
@@ -654,14 +703,35 @@ private final void writeChars(final CharSequence csq) throws IOException {
afterValue();
}
+ /**
+ * Writes the given {@link Date} as an ISO-8601 formatted JSON string in UTC.
+ *
+ * @param val the date value
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeDate(final Date val) throws IOException {
return writeDate(DateUtils.toZonedDateTimeUtc(val));
}
+ /**
+ * Writes the given {@link Instant} as an ISO-8601 formatted JSON string in UTC.
+ *
+ * @param val the instant value
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeDate(final Instant val) throws IOException {
return writeDate(DateUtils.toZonedDateTimeUtc(val));
}
+ /**
+ * Writes the given {@link ZonedDateTime} as an ISO-8601 formatted JSON string.
+ *
+ * @param val the zoned date time value
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeDate(final ZonedDateTime val) throws IOException {
final String iso = DateUtils.toStringIsoFormat(val);
writeString(iso);
@@ -690,6 +760,12 @@ public final JsonWriter writeDouble(final Double val) throws IOException {
return this;
}
+ /**
+ * Writes the end of the current JSON array.
+ *
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeEndArray() throws IOException {
final boolean notEmpty = this.state[indent--].writeEndArray();
if (notEmpty) {
@@ -698,6 +774,12 @@ public final JsonWriter writeEndArray() throws IOException {
return this;
}
+ /**
+ * Writes the end of the current JSON object.
+ *
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeEndObject() throws IOException {
final boolean notEmpty = this.state[indent--].writeEndObject();
if (notEmpty) {
@@ -728,11 +810,25 @@ public final JsonWriter writeFloat(final Float val) throws IOException {
return this;
}
+ /**
+ * Sets the key for the next value to be written within a JSON object.
+ *
+ * @param key the key name
+ * @return this writer for chaining
+ */
public final JsonWriter writeKey(final Object key) {
this.state[indent].writeKey(key);
return this;
}
+ /**
+ * Writes a key-value pair where the value is written without JSON string escaping.
+ *
+ * @param key the key name
+ * @param value the value to write unescaped
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeKeyUnescapedValue(final Object key, final Object value)
throws IOException {
if (skipEmpty && isEmpty(value) || skipNulls && isNull(value)) {
@@ -743,6 +839,14 @@ public final JsonWriter writeKeyUnescapedValue(final Object key, final Object
return this;
}
+ /**
+ * Writes a key-value pair within a JSON object.
+ *
+ * @param key the key name
+ * @param value the value to write
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeKeyValue(final Object key, final Object value) throws IOException {
if (skipEmpty && isEmpty(value) || skipNulls && isNull(value)) {
return this;
@@ -752,6 +856,13 @@ public final JsonWriter writeKeyValue(final Object key, final Object value) t
return this;
}
+ /**
+ * Writes the given {@link Collection} as a JSON array.
+ *
+ * @param collection the collection to write, or {@code null} to write a JSON null
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeList(final Collection collection) throws IOException {
if (collection == null) {
return writeNull();
@@ -775,6 +886,13 @@ public final JsonWriter writeList(final Collection collection) throws IOExcep
return this;
}
+ /**
+ * Writes the given array of objects as a JSON array.
+ *
+ * @param objects the objects to write, or {@code null} to write a JSON null
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeList(final Object... objects) throws IOException {
if (objects == null) {
return writeNull();
@@ -796,6 +914,13 @@ public final JsonWriter writeList(final Object... objects) throws IOException
return this;
}
+ /**
+ * Writes the given {@link Map} as a JSON object.
+ *
+ * @param map the map to write, or {@code null} to write a JSON null
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeMap(final Map map) throws IOException {
if (map == null) {
return writeNull();
@@ -823,6 +948,12 @@ private final JsonWriter writeNewline() throws IOException {
return this;
}
+ /**
+ * Writes a JSON null value. If skip-nulls or skip-empty is enabled, the null may be suppressed.
+ *
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeNull() throws IOException {
if (skipEmpty || skipNulls) {
return this;
@@ -872,6 +1003,14 @@ public final JsonWriter writeNumber(final Number val) throws IOException {
return this;
}
+ /**
+ * Writes the given Java object as the appropriate JSON type, dispatching based on the object's
+ * runtime type.
+ *
+ * @param val the object to write, or {@code null} to write a JSON null
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeObject(final Object val) throws IOException {
if (val == null) {
return writeNull();
@@ -925,16 +1064,33 @@ public final JsonWriter writeObject(final Object val) throws IOException {
return this;
}
+ /**
+ * Begins writing a new JSON array.
+ *
+ * @return this writer for chaining
+ */
public final JsonWriter writeStartArray() {
this.state[++indent].startArray();
return this;
}
+ /**
+ * Begins writing a new JSON object.
+ *
+ * @return this writer for chaining
+ */
public final JsonWriter writeStartObject() {
this.state[++indent].startObject();
return this;
}
+ /**
+ * Writes the given {@link CharSequence} as a properly escaped JSON string value.
+ *
+ * @param csq the string to write, or {@code null} to write a JSON null
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeString(final CharSequence csq) throws IOException {
if (csq == null) {
return writeNull();
@@ -949,6 +1105,14 @@ public final JsonWriter writeString(final CharSequence csq) throws IOExceptio
return this;
}
+ /**
+ * Writes the given value directly to the output without JSON string escaping. This is useful for
+ * writing pre-formatted JSON or raw numeric values.
+ *
+ * @param val the value to write without escaping
+ * @return this writer for chaining
+ * @throws IOException if an I/O error occurs
+ */
public final JsonWriter writeUnescapedString(final Object val) throws IOException {
beforeValue();
if (val instanceof CharSequence) {
diff --git a/src/main/java/com/arakelian/json/Nullable.java b/src/main/java/com/arakelian/json/Nullable.java
index 4bf3cca..6713008 100644
--- a/src/main/java/com/arakelian/json/Nullable.java
+++ b/src/main/java/com/arakelian/json/Nullable.java
@@ -22,6 +22,10 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+/**
+ * Annotation indicating that a field, method, parameter, or type may hold a {@code null} value.
+ * Used with Immutables to mark optional properties.
+ */
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.TYPE,
ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
diff --git a/src/test/java/com/arakelian/json/JsonFilterTest.java b/src/test/java/com/arakelian/json/JsonFilterTest.java
index 653962d..9e5108e 100644
--- a/src/test/java/com/arakelian/json/JsonFilterTest.java
+++ b/src/test/java/com/arakelian/json/JsonFilterTest.java
@@ -22,6 +22,10 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+/**
+ * Tests for {@link JsonFilter}, verifying include/exclude filtering, pretty-printing, and
+ * compaction of JSON documents.
+ */
public class JsonFilterTest {
@Test
public void testExcludeComplex() throws IOException {
diff --git a/src/test/java/com/arakelian/json/JsonReaderTest.java b/src/test/java/com/arakelian/json/JsonReaderTest.java
index aa642f4..4680b93 100644
--- a/src/test/java/com/arakelian/json/JsonReaderTest.java
+++ b/src/test/java/com/arakelian/json/JsonReaderTest.java
@@ -36,6 +36,9 @@
import com.arakelian.json.JsonReader.JsonToken;
+/**
+ * Tests for {@link JsonReader}, verifying correct tokenization and parsing of JSON input.
+ */
public class JsonReaderTest {
private Object[] parse(final String json) throws IOException {
final List