io.github.stewseo.client.json.JsonpUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of yelp-fusion-client Show documentation
Show all versions of yelp-fusion-client Show documentation
java client to build api objects, handle http transport, and parse/deserialize/serialize json to/from json
package io.github.stewseo.client.json;
import io.github.stewseo.client.util.AllowForbiddenApis;
import jakarta.json.JsonException;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import jakarta.json.spi.JsonProvider;
import jakarta.json.stream.JsonGenerator;
import jakarta.json.stream.JsonLocation;
import jakarta.json.stream.JsonParser;
import java.io.StringReader;
import java.io.Writer;
import java.util.AbstractMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
/**
* JsonpUtils
*/
public class JsonpUtils {
@AllowForbiddenApis("Implementation of the JsonProvider lookup")
public static JsonProvider provider() {
RuntimeException exception;
try {
return JsonProvider.provider();
} catch(RuntimeException re) {
exception = re;
}
// Not found from the thread's context classloader. Try from our own classloader which should be a descendant of an app-server
// classloader if any, and if it still fails try from the SPI class which hopefully will be close to the implementation.
try {
return ServiceLoader.load(JsonProvider.class, JsonpUtils.class.getClassLoader()).iterator().next();
} catch(Exception e) {
// ignore
}
try {
return ServiceLoader.load(JsonProvider.class, JsonProvider.class.getClassLoader()).iterator().next();
} catch(Exception e) {
// ignore
}
throw new JsonException("Unable to get a JsonProvider. Check your classpath or thread context classloader.", exception);
}
public static JsonParser.Event expectNextEvent(JsonParser parser, JsonParser.Event expected) {
JsonParser.Event event = parser.next();
expectEvent(parser, expected, event);
return event;
}
public static void expectEvent(JsonParser parser, JsonParser.Event expected, JsonParser.Event event) {
if (event != expected) {
throw new UnexpectedJsonEventException(parser, event, expected);
}
}
public static String expectKeyName(JsonParser parser, JsonParser.Event event) {
JsonpUtils.expectEvent(parser, JsonParser.Event.KEY_NAME, event);
return parser.getString();
}
public static void ensureAccepts(JsonpDeserializer> deserializer, JsonParser parser, JsonParser.Event event) {
if (!deserializer.acceptedEvents().contains(event)) {
throw new UnexpectedJsonEventException(parser, event, deserializer.acceptedEvents());
}
}
public static void ensureCustomVariantsAllowed(JsonParser parser, JsonpMapper mapper) {
if (mapper.attribute(JsonpMapperFeatures.FORBID_CUSTOM_VARIANTS, false)) {
throw new JsonpMappingException("Json mapper configuration forbids custom variants", parser.getLocation());
}
}
public static void skipValue(JsonParser parser) {
skipValue(parser, parser.next());
}
public static void skipValue(JsonParser parser, JsonParser.Event event) {
switch (event) {
case START_OBJECT -> parser.skipObject();
case START_ARRAY -> parser.skipArray();
default -> {
}
// Not a structure, no additional skipping needed
}
}
public static void serialize(T value, JsonGenerator generator, JsonpSerializer serializer, JsonpMapper mapper) {
if (serializer != null) {
serializer.serialize(value, generator, mapper);
} else if (value instanceof JsonpSerializable) {
((JsonpSerializable) value).serialize(generator, mapper);
} else {
mapper.serialize(value, generator);
}
}
/**
* Looks ahead a field value in the Json object from the upcoming object in a parser, which should be on the
* START_OBJECT event.
* Returns a pair containing that value and a parser that should be used to actually parse the object
* (the object has been consumed from the original one).
*/
public static Map.Entry lookAheadFieldValue(
String name, String defaultValue, JsonParser parser, JsonpMapper mapper
) {
// FIXME: need a buffering parser wrapper so that we don't roundtrip through a JsonObject and a String
JsonLocation location = parser.getLocation();
JsonObject object = parser.getObject();
String result = object.getString(name, null);
if (result == null) {
result = defaultValue;
}
if (result == null) {
throw new JsonpMappingException("Property '" + name + "' not found", location);
}
JsonParser newParser = objectParser(object, mapper);
// Pin location to the start of the look ahead, as the new parser will return locations in its own buffer
newParser = new DelegatingJsonParser(newParser) {
@Override
public JsonLocation getLocation() {
return new JsonLocationImpl(location.getLineNumber(), location.getColumnNumber(), location.getStreamOffset()) {
@Override
public String toString() {
return "(in object at " + super.toString().substring(1);
}
};
}
};
return new AbstractMap.SimpleImmutableEntry<>(result, newParser);
}
/**
* Create a parser that traverses a JSON object
*/
public static JsonParser objectParser(JsonObject object, JsonpMapper mapper) {
// FIXME: we should have used createParser(object), but this doesn't work as it creates a
// org.glassfish.json.JsonStructureParser that doesn't implement the JsonP 1.0.1 features, in particular
// parser.getObject(). So deserializing recursive internally-tagged union would fail with UnsupportedOperationException
// While glassfish has this issue or until we write our own, we roundtrip through a string.
String strObject = object.toString();
return mapper.jsonProvider().createParser(new StringReader(strObject));
}
public static String toString(JsonValue value) {
return switch (value.getValueType()) {
case OBJECT -> throw new IllegalArgumentException("Json objects cannot be used as string");
case ARRAY -> value.asJsonArray().stream()
.map(JsonpUtils::toString)
.collect(Collectors.joining(","));
case STRING -> ((JsonString) value).getString();
case TRUE -> "true";
case FALSE -> "false";
case NULL -> "null";
case NUMBER -> value.toString();
};
}
public static void serializeDoubleOrNull(JsonGenerator generator, double value, double defaultValue) {
if (!Double.isFinite(value)) {
generator.writeNull();
} else {
generator.write(value);
}
}
public static void serializeIntOrNull(JsonGenerator generator, int value, int defaultValue) {
if (value == defaultValue && (defaultValue == Integer.MAX_VALUE || defaultValue == Integer.MIN_VALUE)) {
generator.writeNull();
} else {
generator.write(value);
}
}
// serialize to JSON
public static String toString(JsonpSerializable value) {
StringBuilder sb = new StringBuilder();
return toString(value, ToStringMapper.INSTANCE, sb).toString();
}
public static void maxToStringLength(int length) {
MAX_TO_STRING_LENGTH = length;
}
public static int maxToStringLength() {
return MAX_TO_STRING_LENGTH;
}
@Deprecated
public static int MAX_TO_STRING_LENGTH = 10000;
private static class ToStringTooLongException extends RuntimeException {
}
public static StringBuilder toString(JsonpSerializable value, JsonpMapper mapper, StringBuilder dest) {
Writer writer = new Writer() {
int length = 0;
@SuppressWarnings("NullableProblems")
@Override
public void write(char[] cbuf, int off, int len) {
int max = maxToStringLength();
length += len;
if (length > max) {
dest.append(cbuf, off, len - (length - max));
dest.append("...");
throw new ToStringTooLongException();
} else {
dest.append(cbuf, off, len);
}
}
@Override
public void flush() {
}
@Override
public void close() {
}
};
try(JsonGenerator generator = mapper.jsonProvider().createGenerator(writer)) {
value.serialize(generator, mapper);
} catch (ToStringTooLongException e) {
// Ignore
}
return dest;
}
public static StringBuilder toString(JsonpSerializable value, StringBuilder dest) {
return toString(value, ToStringMapper.INSTANCE, dest);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy