com.github.underscore.Json Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of underscore21 Show documentation
Show all versions of underscore21 Show documentation
The java 21 port of Underscore.js
/*
* The MIT License (MIT)
*
* Copyright 2015-2024 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.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@SuppressWarnings({"java:S3740", "java:S3776"})
public final class Json {
private static final int PARSE_MAX_DEPTH = 10_000;
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 indent;
Step(int indent) {
this.indent = indent;
}
public int getIndent() {
return indent;
}
}
private final StringBuilder builder;
private final Step identStep;
private int indent;
public JsonStringBuilder(Step identStep) {
builder = new StringBuilder();
this.identStep = identStep;
}
public JsonStringBuilder() {
builder = new StringBuilder();
this.identStep = Step.TWO_SPACES;
}
public JsonStringBuilder append(final char character) {
builder.append(character);
return this;
}
public JsonStringBuilder append(final String string) {
builder.append(string);
return this;
}
public JsonStringBuilder fillSpaces() {
for (int index = 0; index < indent; index += 1) {
builder.append(identStep == Step.TABS ? '\t' : ' ');
}
return this;
}
public JsonStringBuilder incIndent() {
indent += identStep.getIndent();
return this;
}
public JsonStringBuilder decIndent() {
indent -= identStep.getIndent();
return this;
}
public JsonStringBuilder newLine() {
if (identStep != Step.COMPACT) {
builder.append('\n');
}
return this;
}
public Step getIdentStep() {
return identStep;
}
public String toString() {
return builder.toString();
}
}
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('[').incIndent();
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().decIndent().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('[').incIndent().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().decIndent().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('[').incIndent().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().decIndent().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('[').incIndent().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().decIndent().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('[').incIndent().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().decIndent().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('[').incIndent().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().decIndent().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('[').incIndent().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().decIndent().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('[').incIndent().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().decIndent().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('[').incIndent().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().decIndent().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().incIndent().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().decIndent().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('{').incIndent();
if (!map.isEmpty()) {
builder.newLine();
}
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
builder.fillSpaces().append('"');
builder.append(JsonValue.escape(String.valueOf(entry.getKey())));
builder.append('"');
builder.append(':');
if (builder.getIdentStep() != JsonStringBuilder.Step.COMPACT) {
builder.append(' ');
}
JsonValue.writeJson(entry.getValue(), builder);
if (iter.hasNext()) {
builder.append(',').newLine();
}
}
builder.newLine().decIndent().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('"').append(escape((String) value)).append('"');
} 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('"').append(escape(value.toString())).append('"');
}
}
public static String escape(String s) {
if (s == null) {
return null;
}
StringBuilder sb = new StringBuilder();
escape(s, sb);
return sb.toString();
}
private static void escape(String s, StringBuilder sb) {
final int len = s.length();
for (int i = 0; i < len; i++) {
char ch = s.charAt(i);
switch (ch) {
case '"':
sb.append("\\\"");
break;
case '\\':
sb.append("\\\\");
break;
case '\b':
sb.append("\\b");
break;
case '\f':
sb.append("\\f");
break;
case '\n':
sb.append("\\n");
break;
case '\r':
sb.append("\\r");
break;
case '\t':
sb.append("\\t");
break;
case '€':
sb.append('€');
break;
default:
if (ch <= '\u001F'
|| ch >= '\u007F' && ch <= '\u009F'
|| ch >= '\u2000' && ch <= '\u20FF') {
String ss = Integer.toHexString(ch);
sb.append("\\u");
sb.append("0".repeat(4 - ss.length()));
sb.append(ss.toUpperCase());
} else {
sb.append(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(String.format("%s at %d:%d", message, 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;
private final int maxDepth;
public JsonParser(String string, int maxDepth) {
this.json = string;
this.maxDepth = maxDepth;
line = 1;
captureStart = -1;
}
public Object parse() {
read();
skipWhiteSpace();
final Object result = readValue(0);
skipWhiteSpace();
if (!isEndOfText()) {
throw error("Unexpected character");
}
return result;
}
private Object readValue(int depth) {
if (depth > maxDepth) {
throw error("Maximum depth exceeded");
}
switch (current) {
case 'n':
return readNull();
case 't':
return readTrue();
case 'f':
return readFalse();
case '"':
return readString();
case '[':
return readArray(depth + 1);
case '{':
return readObject(depth + 1);
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 - 2024 Weber Informatics LLC | Privacy Policy