IuJson.java
/*
* Copyright © 2025 Indiana University
* All rights reserved.
*
* BSD 3-Clause License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package edu.iu.client;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.lang.reflect.Type;
import java.net.http.HttpResponse;
import java.util.Map;
import java.util.Objects;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import iu.client.JsonProxy;
import jakarta.json.JsonArray;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonConfig;
import jakarta.json.JsonConfig.KeyStrategy;
import jakarta.json.JsonNumber;
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import jakarta.json.spi.JsonProvider;
/**
* JSON-P processing utilities.
*/
public class IuJson {
/**
* Singleton {@link JsonProvider}.
*/
public static final JsonProvider PROVIDER;
static {
final var current = Thread.currentThread();
final var contextToRestore = current.getContextClassLoader();
try {
current.setContextClassLoader(JsonProvider.class.getClassLoader());
PROVIDER = JsonProvider.provider();
} finally {
current.setContextClassLoader(contextToRestore);
}
}
private static final JsonBuilderFactory FACTORY = PROVIDER
.createBuilderFactory(Map.of(JsonConfig.KEY_STRATEGY, KeyStrategy.NONE));
/**
* Parses a JSON value from an HTTP response.
*
* @param serialized raw serialized JSON input stream
* @return {@link JsonValue}
*/
public static JsonValue parse(HttpResponse<InputStream> serialized) {
return parse(serialized.body());
}
/**
* Parses a JSON value from serialized form.
*
* @param serialized raw serialized JSON input stream
* @return {@link JsonValue}
*/
public static JsonValue parse(InputStream serialized) {
return PROVIDER.createReader(serialized).readValue();
}
/**
* Parses a JSON value from serialized form.
*
* @param serialized raw serialized JSON data, as generated by
* {@link JsonValue#toString()}
* @return {@link JsonValue}
*/
public static JsonValue parse(String serialized) {
if (serialized == null)
return null;
else
return PROVIDER.createReader(new StringReader(serialized)).readValue();
}
/**
* Serializes a JSON value to an {@link OutputStream}.
*
* @param value {@link JsonValue}
* @param out {@link OutputStream}
*/
public static void serialize(JsonValue value, OutputStream out) {
PROVIDER.createWriter(out).write(value);
}
/**
* Wraps a JSON object in a java interface.
*
* @param <T> target interface type
* @param value value
* @param targetInterface target interface class
* @return {@link JsonProxy}
*/
public static <T> T wrap(JsonObject value, Class<T> targetInterface) {
return wrap(value, targetInterface, IuJsonAdapter::of);
}
/**
* Wraps a JSON object in a java interface.
*
* @param <T> target interface type
* @param value value
* @param targetInterface target interface class
* @param valueAdapter transform function: receives a {@link JsonValue} and
* method return type, if custom handling returns an
* object other than the original {@link JsonValue value}
* @return {@link JsonProxy}
*/
public static <T> T wrap(JsonObject value, Class<T> targetInterface,
Function<Type, IuJsonAdapter<?>> valueAdapter) {
return JsonProxy.wrap(value, targetInterface, valueAdapter);
}
/**
* Retrieves the {@link JsonObject} from a {@link JsonProxy} wrapper.
*
* @param jsonProxy wrapper
* @return {@link JsonObject}
*/
public static JsonObject unwrap(Object jsonProxy) {
return JsonProxy.unwrap(jsonProxy);
}
/**
* Creates a JSON value from a {@link Boolean#TYPE boolean}.
*
* @param value {@link Boolean#TYPE boolean}
* @return {@link JsonValue}
*/
public static JsonValue bool(boolean value) {
return value ? JsonValue.TRUE : JsonValue.FALSE;
}
/**
* Creates a JSON value from a {@link Number}.
*
* @param value {@link Number}
* @return {@link JsonNumber}
*/
public static JsonNumber number(Number value) {
return PROVIDER.createValue(value);
}
/**
* Creates a JSON value from a {@link String}.
*
* @param value {@link String}
* @return {@link JsonString}
*/
public static JsonString string(String value) {
return PROVIDER.createValue(value);
}
/**
* Creates an array builder that rejects duplicate values.
*
* @return {@link JsonArrayBuilder}
*/
public static JsonArrayBuilder array() {
return FACTORY.createArrayBuilder();
}
/**
* Creates a builder for modifying an array.
*
* @param array array to copy
* @return {@link JsonArrayBuilder}
*/
public static JsonArrayBuilder array(JsonArray array) {
return PROVIDER.createArrayBuilder(array);
}
/**
* Creates an object builder that rejects duplicate values.
*
* @return {@link JsonObjectBuilder}
*/
public static JsonObjectBuilder object() {
return FACTORY.createObjectBuilder();
}
/**
* Creates a builder for modifying an object.
*
* @param object object to copy
* @return {@link JsonObjectBuilder}
*/
public static JsonObjectBuilder object(JsonObject object) {
return PROVIDER.createObjectBuilder(object);
}
/**
* Adds a value to an object builder.
*
* @param builder {@link JsonObjectBuilder}
* @param name name
* @param value value
*/
public static void add(JsonObjectBuilder builder, String name, Object value) {
add(builder, name, () -> value, () -> true, IuJsonAdapter.basic());
}
/**
* Adds a value to an object builder.
*
* @param <T> value type
* @param builder {@link JsonObjectBuilder}
* @param name property name
* @param valueSupplier value supplier; if null is returned, the property will be undefined
* @param adapter JSON type adapter for handling non-null values
*/
public static <T> void add(JsonObjectBuilder builder, String name, Supplier<T> valueSupplier,
IuJsonAdapter<T> adapter) {
add(builder, name, valueSupplier, () -> true, adapter);
}
/**
* Adds a value to an object builder.
*
* @param <T> value type
* @param builder {@link JsonObjectBuilder}
* @param name property name
* @param value value
* @param condition supplies true if the value should be added; false to do
* nothing
*/
public static <T> void add(JsonObjectBuilder builder, String name, T value, BooleanSupplier condition) {
add(builder, name, () -> value, condition, IuJsonAdapter.basic());
}
/**
* Adds a value to an object builder.
*
* @param <T> value type
* @param builder {@link JsonObjectBuilder}
* @param name property name
* @param valueSupplier value supplier
* @param condition supplies true if the value should be added; false to do
* nothing
* @param adapter JSON type adapter for handling non-null values
*/
public static <T> void add(JsonObjectBuilder builder, String name, Supplier<T> valueSupplier,
BooleanSupplier condition, IuJsonAdapter<T> adapter) {
if (!condition.getAsBoolean())
return;
final var a = valueSupplier.get();
if (a != null)
builder.add(name, adapter.toJson(a));
}
/**
* Adds a value to an array builder.
*
* @param <T> value type
* @param builder {@link JsonArrayBuilder}
* @param value value
*/
public static <T> void add(JsonArrayBuilder builder, T value) {
add(builder, () -> value, () -> true, IuJsonAdapter.basic());
}
/**
* Adds a value to an array builder.
*
* @param <T> value type
* @param builder {@link JsonArrayBuilder}
* @param valueSupplier value supplier
* @param adapter JSON type adapter for handling non-null values
*/
public static <T> void add(JsonArrayBuilder builder, Supplier<T> valueSupplier, IuJsonAdapter<T> adapter) {
add(builder, valueSupplier, () -> true, adapter);
}
/**
* Adds a value to an array builder.
*
* @param <T> value type
* @param builder {@link JsonArrayBuilder}
* @param value value
* @param condition supplies true if the value should be added; false to do
* nothing
*/
public static <T> void add(JsonArrayBuilder builder, T value, BooleanSupplier condition) {
add(builder, () -> value, condition, IuJsonAdapter.basic());
}
/**
* Adds a value to an array builder.
*
* @param <T> value type
* @param builder {@link JsonArrayBuilder}
* @param valueSupplier value supplier
* @param condition supplies true if the value should be added; false to do
* nothing
* @param adapter JSON type adapter for handling non-null values
*/
public static <T> void add(JsonArrayBuilder builder, Supplier<T> valueSupplier, BooleanSupplier condition,
IuJsonAdapter<T> adapter) {
if (!condition.getAsBoolean())
return;
final var a = valueSupplier.get();
if (a != null)
builder.add(adapter.toJson(a));
}
/**
* Gets a property value from a JSON object.
*
* @param <T> result type
* @param object {@link JsonObject}
* @param name property name
* @return property value
*/
@SuppressWarnings("unchecked")
public static <T> T get(JsonObject object, String name) {
return (T) get(object, name, null, IuJsonAdapter.of(Object.class));
}
/**
* Gets a property value from a JSON object.
*
* @param <T> result type
* @param object {@link JsonObject}
* @param name property name
* @param adapter adapter for converting non-null values
* @return property value
*/
public static <T> T get(JsonObject object, String name, IuJsonAdapter<T> adapter) {
return get(object, name, null, adapter);
}
/**
* Gets a non-null property value from a JSON object.
*
* @param <T> result type
* @param object {@link JsonObject}
* @param name property name
* @param adapter adapter for converting non-null values
* @return property value
* @throws NullPointerException if the property value is {@link JsonValue#NULL}
*/
public static <T> T nonNull(JsonObject object, String name, IuJsonAdapter<T> adapter) {
return Objects.requireNonNull(get(object, name, null, adapter), name);
}
/**
* Gets a property value from a JSON object, accepting if non-null.
*
* @param <T> result type
* @param object {@link JsonObject}
* @param name property name
* @param consumer accepts the property value if non-null; else skipped
*/
public static <T> void get(JsonObject object, String name, Consumer<T> consumer) {
get(object, name, IuJsonAdapter.basic(), consumer);
}
/**
* Gets a property value from a JSON object, accepting if non-null..
*
* @param <T> result type
* @param object {@link JsonObject}
* @param name property name
* @param adapter JSON type adapter
* @param consumer accepts the property value if non-null; else skipped
*/
public static <T> void get(JsonObject object, String name, IuJsonAdapter<T> adapter, Consumer<T> consumer) {
final var value = get(object, name, null, adapter);
if (value != null)
consumer.accept(value);
}
/**
* Gets a property value from a JSON object.
*
* @param <T> result type
* @param object {@link JsonObject}
* @param name property name
* @param defaultValue value to return if the property is missing
* @param adapter adapter for converting non-null values to Java
* @return property value, or defaultValue if the property is missing
*/
public static <T> T get(JsonObject object, String name, T defaultValue, IuJsonAdapter<T> adapter) {
final var v = object.get(name);
if (v == null)
return defaultValue;
else
return adapter.fromJson(v);
}
private IuJson() {
}
}