
com.github.underscore.Json Maven / Gradle / Ivy
/*
* The MIT License (MIT)
*
* Copyright 2015-2022 Valentyn Kolesnikov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.github.underscore;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
@SuppressWarnings({"java:S3740", "java:S3776"})
public final class Json {
private Json() {}
private static final String NULL = "null";
private static final String DIGIT = "digit";
public static class JsonStringBuilder {
public enum Step {
TWO_SPACES(2),
THREE_SPACES(3),
FOUR_SPACES(4),
COMPACT(0),
TABS(1);
private final int ident;
Step(int ident) {
this.ident = ident;
}
public int getIdent() {
return ident;
}
}
public enum Type {
PURE("", "\n", "", "\""),
JAVA("\"", "\\n\"\n + \"", "\";", "\\\"");
private final String initial;
private final String newLine;
private final String tailLine;
private final String wrapLine;
Type(String initial, String newLine, String tailLine, String wrapLine) {
this.initial = initial;
this.newLine = newLine;
this.tailLine = tailLine;
this.wrapLine = wrapLine;
}
public String getInitial() {
return initial;
}
public String getNewLine() {
return newLine;
}
public String getTailLine() {
return tailLine;
}
public String getWrapLine() {
return wrapLine;
}
}
private final StringJoiner builder;
private final Step identStep;
private final Type type;
private int ident;
public JsonStringBuilder(Step identStep) {
builder = new StringJoiner("").add(Type.PURE.getInitial());
this.identStep = identStep;
this.type = Type.PURE;
}
public JsonStringBuilder(Type type) {
builder = new StringJoiner("").add(type.getInitial());
this.identStep = Step.TWO_SPACES;
this.type = type;
}
public JsonStringBuilder() {
builder = new StringJoiner("");
this.identStep = Step.TWO_SPACES;
this.type = Type.PURE;
}
public JsonStringBuilder append(final char character) {
builder.add(String.valueOf(character));
return this;
}
public JsonStringBuilder append(final String string) {
builder.add(string);
return this;
}
public JsonStringBuilder fillSpaces() {
for (int index = 0; index < ident; index += 1) {
builder.add(String.valueOf(identStep == Step.TABS ? '\t' : ' '));
}
return this;
}
public JsonStringBuilder incIdent() {
ident += identStep.getIdent();
return this;
}
public JsonStringBuilder decIdent() {
ident -= identStep.getIdent();
return this;
}
public JsonStringBuilder newLine() {
if (identStep != Step.COMPACT) {
builder.add(type.getNewLine());
}
return this;
}
public Step getIdentStep() {
return identStep;
}
public String toString() {
return builder.toString() + type.getTailLine();
}
}
public static class JsonArray {
private JsonArray() {}
public static void writeJson(Collection collection, JsonStringBuilder builder) {
if (collection == null) {
builder.append(NULL);
return;
}
Iterator iter = collection.iterator();
builder.append('[').incIdent();
if (!collection.isEmpty()) {
builder.newLine();
}
while (iter.hasNext()) {
Object value = iter.next();
builder.fillSpaces();
JsonValue.writeJson(value, builder);
if (iter.hasNext()) {
builder.append(',').newLine();
}
}
builder.newLine().decIdent().fillSpaces().append(']');
}
public static void writeJson(byte[] array, JsonStringBuilder builder) {
if (array == null) {
builder.append(NULL);
} else if (array.length == 0) {
builder.append("[]");
} else {
builder.append('[').incIdent().newLine();
builder.fillSpaces().append(String.valueOf(array[0]));
for (int i = 1; i < array.length; i++) {
builder.append(',').newLine().fillSpaces();
builder.append(String.valueOf(array[i]));
}
builder.newLine().decIdent().fillSpaces().append(']');
}
}
public static void writeJson(short[] array, JsonStringBuilder builder) {
if (array == null) {
builder.append(NULL);
} else if (array.length == 0) {
builder.append("[]");
} else {
builder.append('[').incIdent().newLine();
builder.fillSpaces().append(String.valueOf(array[0]));
for (int i = 1; i < array.length; i++) {
builder.append(',').newLine().fillSpaces();
builder.append(String.valueOf(array[i]));
}
builder.newLine().decIdent().fillSpaces().append(']');
}
}
public static void writeJson(int[] array, JsonStringBuilder builder) {
if (array == null) {
builder.append(NULL);
} else if (array.length == 0) {
builder.append("[]");
} else {
builder.append('[').incIdent().newLine();
builder.fillSpaces().append(String.valueOf(array[0]));
for (int i = 1; i < array.length; i++) {
builder.append(',').newLine().fillSpaces();
builder.append(String.valueOf(array[i]));
}
builder.newLine().decIdent().fillSpaces().append(']');
}
}
public static void writeJson(long[] array, JsonStringBuilder builder) {
if (array == null) {
builder.append(NULL);
} else if (array.length == 0) {
builder.append("[]");
} else {
builder.append('[').incIdent().newLine();
builder.fillSpaces().append(String.valueOf(array[0]));
for (int i = 1; i < array.length; i++) {
builder.append(',').newLine().fillSpaces();
builder.append(String.valueOf(array[i]));
}
builder.newLine().decIdent().fillSpaces().append(']');
}
}
public static void writeJson(float[] array, JsonStringBuilder builder) {
if (array == null) {
builder.append(NULL);
} else if (array.length == 0) {
builder.append("[]");
} else {
builder.append('[').incIdent().newLine();
builder.fillSpaces().append(String.valueOf(array[0]));
for (int i = 1; i < array.length; i++) {
builder.append(',').newLine().fillSpaces();
builder.append(String.valueOf(array[i]));
}
builder.newLine().decIdent().fillSpaces().append(']');
}
}
public static void writeJson(double[] array, JsonStringBuilder builder) {
if (array == null) {
builder.append(NULL);
} else if (array.length == 0) {
builder.append("[]");
} else {
builder.append('[').incIdent().newLine();
builder.fillSpaces().append(String.valueOf(array[0]));
for (int i = 1; i < array.length; i++) {
builder.append(',').newLine().fillSpaces();
builder.append(String.valueOf(array[i]));
}
builder.newLine().decIdent().fillSpaces().append(']');
}
}
public static void writeJson(boolean[] array, JsonStringBuilder builder) {
if (array == null) {
builder.append(NULL);
} else if (array.length == 0) {
builder.append("[]");
} else {
builder.append('[').incIdent().newLine();
builder.fillSpaces().append(String.valueOf(array[0]));
for (int i = 1; i < array.length; i++) {
builder.append(',').newLine().fillSpaces();
builder.append(String.valueOf(array[i]));
}
builder.newLine().decIdent().fillSpaces().append(']');
}
}
public static void writeJson(char[] array, JsonStringBuilder builder) {
if (array == null) {
builder.append(NULL);
} else if (array.length == 0) {
builder.append("[]");
} else {
builder.append('[').incIdent().newLine();
builder.fillSpaces().append('\"').append(String.valueOf(array[0])).append('\"');
for (int i = 1; i < array.length; i++) {
builder.append(',').newLine().fillSpaces();
builder.append('\"').append(String.valueOf(array[i])).append('\"');
}
builder.newLine().decIdent().fillSpaces().append(']');
}
}
public static void writeJson(Object[] array, JsonStringBuilder builder) {
if (array == null) {
builder.append(NULL);
} else if (array.length == 0) {
builder.append("[]");
} else {
builder.append('[').newLine().incIdent().fillSpaces();
JsonValue.writeJson(array[0], builder);
for (int i = 1; i < array.length; i++) {
builder.append(',').newLine().fillSpaces();
JsonValue.writeJson(array[i], builder);
}
builder.newLine().decIdent().fillSpaces().append(']');
}
}
}
public static class JsonObject {
private JsonObject() {}
public static void writeJson(Map map, JsonStringBuilder builder) {
if (map == null) {
builder.append(NULL);
return;
}
Iterator iter = map.entrySet().iterator();
builder.append('{').incIdent();
if (!map.isEmpty()) {
builder.newLine();
}
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
builder.fillSpaces().append(builder.type.getWrapLine());
builder.append(JsonValue.escape(String.valueOf(entry.getKey())));
builder.append(builder.type.getWrapLine());
builder.append(':');
if (builder.getIdentStep() != JsonStringBuilder.Step.COMPACT) {
builder.append(' ');
}
JsonValue.writeJson(entry.getValue(), builder);
if (iter.hasNext()) {
builder.append(',').newLine();
}
}
builder.newLine().decIdent().fillSpaces().append('}');
}
}
public static class JsonValue {
private JsonValue() {}
public static void writeJson(Object value, JsonStringBuilder builder) {
if (value == null) {
builder.append(NULL);
} else if (value instanceof String) {
builder.append(builder.type.getWrapLine())
.append(escape((String) value))
.append(builder.type.getWrapLine());
} else if (value instanceof Double) {
if (((Double) value).isInfinite() || ((Double) value).isNaN()) {
builder.append(NULL);
} else {
builder.append(value.toString());
}
} else if (value instanceof Float) {
if (((Float) value).isInfinite() || ((Float) value).isNaN()) {
builder.append(NULL);
} else {
builder.append(value.toString());
}
} else if (value instanceof Number) {
builder.append(value.toString());
} else if (value instanceof Boolean) {
builder.append(value.toString());
} else if (value instanceof Map) {
JsonObject.writeJson((Map) value, builder);
} else if (value instanceof Collection) {
JsonArray.writeJson((Collection) value, builder);
} else {
doWriteJson(value, builder);
}
}
private static void doWriteJson(Object value, JsonStringBuilder builder) {
if (value instanceof byte[]) {
JsonArray.writeJson((byte[]) value, builder);
} else if (value instanceof short[]) {
JsonArray.writeJson((short[]) value, builder);
} else if (value instanceof int[]) {
JsonArray.writeJson((int[]) value, builder);
} else if (value instanceof long[]) {
JsonArray.writeJson((long[]) value, builder);
} else if (value instanceof float[]) {
JsonArray.writeJson((float[]) value, builder);
} else if (value instanceof double[]) {
JsonArray.writeJson((double[]) value, builder);
} else if (value instanceof boolean[]) {
JsonArray.writeJson((boolean[]) value, builder);
} else if (value instanceof char[]) {
JsonArray.writeJson((char[]) value, builder);
} else if (value instanceof Object[]) {
JsonArray.writeJson((Object[]) value, builder);
} else {
builder.append(value.toString());
}
}
public static String escape(String s) {
if (s == null) {
return null;
}
StringJoiner sb = new StringJoiner("");
escape(s, sb);
return sb.toString();
}
private static void escape(String s, StringJoiner sb) {
final int len = s.length();
for (int i = 0; i < len; i++) {
char ch = s.charAt(i);
switch (ch) {
case '"':
sb.add("\\\"");
break;
case '\\':
sb.add("\\\\");
break;
case '\b':
sb.add("\\b");
break;
case '\f':
sb.add("\\f");
break;
case '\n':
sb.add("\\n");
break;
case '\r':
sb.add("\\r");
break;
case '\t':
sb.add("\\t");
break;
case '€':
sb.add("€");
break;
default:
if (ch <= '\u001F'
|| ch >= '\u007F' && ch <= '\u009F'
|| ch >= '\u2000' && ch <= '\u20FF') {
String ss = Integer.toHexString(ch);
sb.add("\\u");
for (int k = 0; k < 4 - ss.length(); k++) {
sb.add("0");
}
sb.add(ss.toUpperCase());
} else {
sb.add(String.valueOf(ch));
}
break;
}
}
}
}
public static class ParseException extends RuntimeException {
private final int offset;
private final int line;
private final int column;
public ParseException(String message, int offset, int line, int column) {
super(message + " at " + line + ":" + column);
this.offset = offset;
this.line = line;
this.column = column;
}
public int getOffset() {
return offset;
}
public int getLine() {
return line;
}
public int getColumn() {
return column;
}
}
public static class JsonParser {
private final String json;
private int index;
private int line;
private int lineOffset;
private int current;
private StringBuilder captureBuffer;
private int captureStart;
public JsonParser(String string) {
this.json = string;
line = 1;
captureStart = -1;
}
public Object parse() {
read();
skipWhiteSpace();
final Object result = readValue();
skipWhiteSpace();
if (!isEndOfText()) {
throw error("Unexpected character");
}
return result;
}
private Object readValue() {
switch (current) {
case 'n':
return readNull();
case 't':
return readTrue();
case 'f':
return readFalse();
case '"':
return readString();
case '[':
return readArray();
case '{':
return readObject();
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return readNumber();
default:
throw expected("value");
}
}
private List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy