All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.stewseo.client.json.JsonpUtils Maven / Gradle / Ivy

Go to download

java client to build api objects, handle http transport, and parse/deserialize/serialize json to/from json

There is a newer version: 1.8.0
Show newest version
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