← Каталог
Java — JSON-парсер (учебная реализация)
Лексер, парсер рекурсивного спуска, объектная модель и сериализатор по RFC 8259.
import java.io.IOException;
import java.io.Reader;
import java.util.Objects;
public final class Json {
private Json() { }
public static JsonValue parse(String json) {
Objects.requireNonNull(json, "json must not be null");
return new JsonParser(new JsonLexer(json)).parse();
}
public static String stringify(JsonValue value) {
Objects.requireNonNull(value, "value must not be null");
return new JsonSerializer().serialize(value);
}
public static JsonValue parse(Reader reader) throws IOException {
StringBuilder sb = new StringBuilder();
char[] buf = new char[8192];
int n;
while ((n = reader.read(buf)) != -1) {
sb.append(buf, 0, n);
}
return parse(sb.toString());
}
} import java.io.IOException;
import java.io.Reader;
import java.util.Objects;
public final class Json {
private Json() { }
public static JsonValue parse(String json) {
Objects.requireNonNull(json, "json must not be null");
return new JsonParser(new JsonLexer(json)).parse();
}
public static String stringify(JsonValue value) {
Objects.requireNonNull(value, "value must not be null");
return new JsonSerializer().serialize(value);
}
public static JsonValue parse(Reader reader) throws IOException {
StringBuilder sb = new StringBuilder();
char[] buf = new char[8192];
int n;
while ((n = reader.read(buf)) != -1) {
sb.append(buf, 0, n);
}
return parse(sb.toString());
}
} import java.math.BigDecimal;
import java.util.Objects;
final class JsonLexer {
private final String input;
private int index = 0;
private int line = 1;
private int column = 1;
private Token bufferedToken = null;
JsonLexer(String input) {
this.input = Objects.requireNonNull(input, "input must not be null");
}
public Token nextToken() {
if (bufferedToken != null) {
Token t = bufferedToken;
bufferedToken = null;
return t;
}
skipWhitespace();
if (index >= input.length()) {
return new EndOfInput(position());
}
char c = input.charAt(index);
int startPos = position();
switch (c) {
case '{' -> { index++; return new LeftBrace(startPos); }
case '}' -> { index++; return new RightBrace(startPos); }
case '[' -> { index++; return new LeftBracket(startPos); }
case ']' -> { index++; return new RightBracket(startPos); }
case ':' -> { index++; return new Colon(startPos); }
case ',' -> { index++; return new Comma(startPos); }
case '"' -> return parseString(startPos);
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> return parseNumber(startPos);
case 't' -> return parseKeyword("true", new TrueToken(startPos), startPos);
case 'f' -> return parseKeyword("false", new FalseToken(startPos), startPos);
case 'n' -> return parseKeyword("null", new NullToken(startPos), startPos);
default -> throw error("Unexpected character: '" + c + "'");
}
}
public Token peek() {
if (bufferedToken == null) {
bufferedToken = nextToken();
}
return bufferedToken;
}
public void putBack(Token token) {
if (bufferedToken != null) {
throw new IllegalStateException("Buffer already full");
}
bufferedToken = token;
}
public int getLine() { return line; }
public int getColumn() { return column; }
private void skipWhitespace() {
while (index < input.length()) {
char c = input.charAt(index);
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
advance();
} else {
break;
}
}
}
private Token parseString(int startPos) {
advance();
StringBuilder sb = new StringBuilder();
while (index < input.length()) {
char c = input.charAt(index);
if (c == '"') {
advance();
return new StringToken(sb.toString(), startPos);
}
if (c == '\\') {
advance();
if (index >= input.length()) {
throw error("Unterminated escape sequence in string");
}
c = input.charAt(index);
switch (c) {
case '"' -> { sb.append('"'); advance(); }
case '\\' -> { sb.append('\\'); advance(); }
case '/' -> { sb.append('/'); advance(); }
case 'b' -> { sb.append('\b'); advance(); }
case 'f' -> { sb.append('\f'); advance(); }
case 'n' -> { sb.append('\n'); advance(); }
case 'r' -> { sb.append('\r'); advance(); }
case 't' -> { sb.append('\t'); advance(); }
case 'u' -> {
advance();
if (index + 4 > input.length()) {
throw error("Incomplete \\u escape: expected 4 hex digits");
}
String hex = input.substring(index, index + 4);
try {
int codePoint = Integer.parseInt(hex, 16);
sb.appendCodePoint(codePoint);
index += 4;
column += 4;
} catch (NumberFormatException e) {
throw error("Invalid hex digits in \\u escape: '" + hex + "'");
}
}
default -> throw error("Invalid escape sequence: \\" + c);
}
} else {
sb.append(c);
advance();
}
}
throw error("Unterminated string");
}
private Token parseNumber(int startPos) {
int start = index;
if (peekChar() == '-') {
advance();
}
if (peekChar() == '0') {
advance();
char next = peekChar();
if (next >= '0' && next <= '9') {
throw error("Invalid number: leading zero");
}
} else if (isDigit(peekChar())) {
do { advance(); } while (isDigit(peekChar()));
} else {
throw error("Invalid number: missing digits after sign");
}
if (peekChar() == '.') {
advance();
if (!isDigit(peekChar())) {
throw error("Invalid number: missing digits after '.'");
}
do { advance(); } while (isDigit(peekChar()));
}
char c = peekChar();
if (c == 'e' || c == 'E') {
advance();
c = peekChar();
if (c == '+' || c == '-') {
advance();
}
if (!isDigit(peekChar())) {
throw error("Invalid number: missing digits after exponent indicator");
}
do { advance(); } while (isDigit(peekChar()));
}
String raw = input.substring(start, index);
try {
BigDecimal value = new BigDecimal(raw);
return new NumberToken(value, raw, startPos);
} catch (NumberFormatException e) {
throw error("Invalid number format: '" + raw + "'");
}
}
private Token parseKeyword(String keyword, Token token, int startPos) {
if (index + keyword.length() > input.length()) {
throw error("Unexpected end of input while parsing '" + keyword + "'");
}
String candidate = input.substring(index, index + keyword.length());
if (!candidate.equals(keyword)) {
throw error("Expected '" + keyword + "', got '" + candidate + "'");
}
index += keyword.length();
column += keyword.length();
return token;
}
private char peekChar() {
return index < input.length() ? input.charAt(index) : '\0';
}
private boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
private void advance() {
if (index >= input.length()) return;
char c = input.charAt(index);
if (c == '\n') {
line++;
column = 1;
} else if (c == '\r') {
if (index + 1 < input.length() && input.charAt(index + 1) == '\n') {
index++;
}
line++;
column = 1;
} else {
column++;
}
index++;
}
private int position() {
return index;
}
private JsonParseException error(String message) {
return new JsonParseException(message, position(), line, column);
}
} import java.math.BigDecimal;
import java.util.Objects;
final class JsonLexer {
private final String input;
private int index = 0;
private int line = 1;
private int column = 1;
private Token bufferedToken = null;
JsonLexer(String input) {
this.input = Objects.requireNonNull(input, "input must not be null");
}
public Token nextToken() {
if (bufferedToken != null) {
Token t = bufferedToken;
bufferedToken = null;
return t;
}
skipWhitespace();
if (index >= input.length()) {
return new EndOfInput(position());
}
char c = input.charAt(index);
int startPos = position();
switch (c) {
case '{' -> { index++; return new LeftBrace(startPos); }
case '}' -> { index++; return new RightBrace(startPos); }
case '[' -> { index++; return new LeftBracket(startPos); }
case ']' -> { index++; return new RightBracket(startPos); }
case ':' -> { index++; return new Colon(startPos); }
case ',' -> { index++; return new Comma(startPos); }
case '"' -> return parseString(startPos);
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> return parseNumber(startPos);
case 't' -> return parseKeyword("true", new TrueToken(startPos), startPos);
case 'f' -> return parseKeyword("false", new FalseToken(startPos), startPos);
case 'n' -> return parseKeyword("null", new NullToken(startPos), startPos);
default -> throw error("Unexpected character: '" + c + "'");
}
}
public Token peek() {
if (bufferedToken == null) {
bufferedToken = nextToken();
}
return bufferedToken;
}
public void putBack(Token token) {
if (bufferedToken != null) {
throw new IllegalStateException("Buffer already full");
}
bufferedToken = token;
}
public int getLine() { return line; }
public int getColumn() { return column; }
private void skipWhitespace() {
while (index < input.length()) {
char c = input.charAt(index);
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
advance();
} else {
break;
}
}
}
private Token parseString(int startPos) {
advance();
StringBuilder sb = new StringBuilder();
while (index < input.length()) {
char c = input.charAt(index);
if (c == '"') {
advance();
return new StringToken(sb.toString(), startPos);
}
if (c == '\\') {
advance();
if (index >= input.length()) {
throw error("Unterminated escape sequence in string");
}
c = input.charAt(index);
switch (c) {
case '"' -> { sb.append('"'); advance(); }
case '\\' -> { sb.append('\\'); advance(); }
case '/' -> { sb.append('/'); advance(); }
case 'b' -> { sb.append('\b'); advance(); }
case 'f' -> { sb.append('\f'); advance(); }
case 'n' -> { sb.append('\n'); advance(); }
case 'r' -> { sb.append('\r'); advance(); }
case 't' -> { sb.append('\t'); advance(); }
case 'u' -> {
advance();
if (index + 4 > input.length()) {
throw error("Incomplete \\u escape: expected 4 hex digits");
}
String hex = input.substring(index, index + 4);
try {
int codePoint = Integer.parseInt(hex, 16);
sb.appendCodePoint(codePoint);
index += 4;
column += 4;
} catch (NumberFormatException e) {
throw error("Invalid hex digits in \\u escape: '" + hex + "'");
}
}
default -> throw error("Invalid escape sequence: \\" + c);
}
} else {
sb.append(c);
advance();
}
}
throw error("Unterminated string");
}
private Token parseNumber(int startPos) {
int start = index;
if (peekChar() == '-') {
advance();
}
if (peekChar() == '0') {
advance();
char next = peekChar();
if (next >= '0' && next <= '9') {
throw error("Invalid number: leading zero");
}
} else if (isDigit(peekChar())) {
do { advance(); } while (isDigit(peekChar()));
} else {
throw error("Invalid number: missing digits after sign");
}
if (peekChar() == '.') {
advance();
if (!isDigit(peekChar())) {
throw error("Invalid number: missing digits after '.'");
}
do { advance(); } while (isDigit(peekChar()));
}
char c = peekChar();
if (c == 'e' || c == 'E') {
advance();
c = peekChar();
if (c == '+' || c == '-') {
advance();
}
if (!isDigit(peekChar())) {
throw error("Invalid number: missing digits after exponent indicator");
}
do { advance(); } while (isDigit(peekChar()));
}
String raw = input.substring(start, index);
try {
BigDecimal value = new BigDecimal(raw);
return new NumberToken(value, raw, startPos);
} catch (NumberFormatException e) {
throw error("Invalid number format: '" + raw + "'");
}
}
private Token parseKeyword(String keyword, Token token, int startPos) {
if (index + keyword.length() > input.length()) {
throw error("Unexpected end of input while parsing '" + keyword + "'");
}
String candidate = input.substring(index, index + keyword.length());
if (!candidate.equals(keyword)) {
throw error("Expected '" + keyword + "', got '" + candidate + "'");
}
index += keyword.length();
column += keyword.length();
return token;
}
private char peekChar() {
return index < input.length() ? input.charAt(index) : '\0';
}
private boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
private void advance() {
if (index >= input.length()) return;
char c = input.charAt(index);
if (c == '\n') {
line++;
column = 1;
} else if (c == '\r') {
if (index + 1 < input.length() && input.charAt(index + 1) == '\n') {
index++;
}
line++;
column = 1;
} else {
column++;
}
index++;
}
private int position() {
return index;
}
private JsonParseException error(String message) {
return new JsonParseException(message, position(), line, column);
}
} final class JsonParseException extends RuntimeException {
private final int position;
private final int line;
private final int column;
JsonParseException(String message, int position, int line, int column) {
super(String.format("%s at line %d, column %d (position %d)", message, line, column, position));
this.position = position;
this.line = line;
this.column = column;
}
public int getPosition() { return position; }
public int getLine() { return line; }
public int getColumn() { return column; }
} final class JsonParseException extends RuntimeException {
private final int position;
private final int line;
private final int column;
JsonParseException(String message, int position, int line, int column) {
super(String.format("%s at line %d, column %d (position %d)", message, line, column, position));
this.position = position;
this.line = line;
this.column = column;
}
public int getPosition() { return position; }
public int getLine() { return line; }
public int getColumn() { return column; }
} import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
final class JsonParser {
private final JsonLexer lexer;
private Token currentToken;
JsonParser(JsonLexer lexer) {
this.lexer = Objects.requireNonNull(lexer, "lexer must not be null");
this.currentToken = lexer.nextToken();
}
public JsonValue parse() {
JsonValue result = parseValue();
if (!(lexer.peek() instanceof EndOfInput)) {
throw error("Unexpected token after top-level value");
}
return result;
}
private JsonValue parseValue() {
return switch (currentToken) {
case LeftBrace _ -> parseObject();
case LeftBracket _ -> parseArray();
case StringToken t -> { consume(); yield new JsonString(t.value()); }
case NumberToken t -> { consume(); yield new JsonNumber(t.value()); }
case TrueToken _ -> { consume(); yield new JsonBoolean(true); }
case FalseToken _ -> { consume(); yield new JsonBoolean(false); }
case NullToken _ -> { consume(); yield JsonNull.getInstance(); }
default -> throw error("Expected value");
};
}
private JsonObject parseObject() {
consume();
Map<String, JsonValue> map = new LinkedHashMap<>();
if (currentToken instanceof RightBrace) {
consume();
return new JsonObject(map);
}
while (true) {
if (!(currentToken instanceof StringToken keyToken)) {
throw error("Expected string (key)");
}
String key = keyToken.value();
consume();
if (!(currentToken instanceof Colon)) {
throw error("Expected ':' after key");
}
consume();
JsonValue value = parseValue();
map.put(key, value);
if (currentToken instanceof Comma) {
consume();
if (currentToken instanceof RightBrace) {
throw error("Trailing comma in object");
}
} else if (currentToken instanceof RightBrace) {
consume();
break;
} else {
throw error("Expected ',' or '}' after value");
}
}
return new JsonObject(map);
}
private JsonArray parseArray() {
consume();
List<JsonValue> list = new ArrayList<>();
if (currentToken instanceof RightBracket) {
consume();
return new JsonArray(list);
}
while (true) {
list.add(parseValue());
if (currentToken instanceof Comma) {
consume();
if (currentToken instanceof RightBracket) {
throw error("Trailing comma in array");
}
} else if (currentToken instanceof RightBracket) {
consume();
break;
} else {
throw error("Expected ',' or ']' after value");
}
}
return new JsonArray(list);
}
private void consume() {
currentToken = lexer.nextToken();
}
private JsonParseException error(String message) {
int pos = currentToken instanceof EndOfInput
? lexer.getColumn()
: tokenPosition(currentToken);
return new JsonParseException(
message + ", got " + tokenName(currentToken),
pos, lexer.getLine(), lexer.getColumn()
);
}
private int tokenPosition(Token t) {
return switch (t) {
case LeftBrace b -> b.position();
case RightBrace b -> b.position();
case LeftBracket b -> b.position();
case RightBracket b -> b.position();
case Colon c -> c.position();
case Comma c -> c.position();
case StringToken s -> s.position();
case NumberToken n -> n.position();
case TrueToken tok -> tok.position();
case FalseToken tok -> tok.position();
case NullToken tok -> tok.position();
case EndOfInput e -> e.position();
};
}
private String tokenName(Token t) {
return switch (t) {
case LeftBrace _ -> "'{'";
case RightBrace _ -> "'}'";
case LeftBracket _ -> "'['";
case RightBracket _ -> "']'";
case Colon _ -> "':'";
case Comma _ -> "','";
case StringToken s -> "string \"" + s.value() + "\"";
case NumberToken n -> "number " + n.raw();
case TrueToken _ -> "'true'";
case FalseToken _ -> "'false'";
case NullToken _ -> "'null'";
case EndOfInput _ -> "end of input";
};
}
} import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
final class JsonParser {
private final JsonLexer lexer;
private Token currentToken;
JsonParser(JsonLexer lexer) {
this.lexer = Objects.requireNonNull(lexer, "lexer must not be null");
this.currentToken = lexer.nextToken();
}
public JsonValue parse() {
JsonValue result = parseValue();
if (!(lexer.peek() instanceof EndOfInput)) {
throw error("Unexpected token after top-level value");
}
return result;
}
private JsonValue parseValue() {
return switch (currentToken) {
case LeftBrace _ -> parseObject();
case LeftBracket _ -> parseArray();
case StringToken t -> { consume(); yield new JsonString(t.value()); }
case NumberToken t -> { consume(); yield new JsonNumber(t.value()); }
case TrueToken _ -> { consume(); yield new JsonBoolean(true); }
case FalseToken _ -> { consume(); yield new JsonBoolean(false); }
case NullToken _ -> { consume(); yield JsonNull.getInstance(); }
default -> throw error("Expected value");
};
}
private JsonObject parseObject() {
consume();
Map<String, JsonValue> map = new LinkedHashMap<>();
if (currentToken instanceof RightBrace) {
consume();
return new JsonObject(map);
}
while (true) {
if (!(currentToken instanceof StringToken keyToken)) {
throw error("Expected string (key)");
}
String key = keyToken.value();
consume();
if (!(currentToken instanceof Colon)) {
throw error("Expected ':' after key");
}
consume();
JsonValue value = parseValue();
map.put(key, value);
if (currentToken instanceof Comma) {
consume();
if (currentToken instanceof RightBrace) {
throw error("Trailing comma in object");
}
} else if (currentToken instanceof RightBrace) {
consume();
break;
} else {
throw error("Expected ',' or '}' after value");
}
}
return new JsonObject(map);
}
private JsonArray parseArray() {
consume();
List<JsonValue> list = new ArrayList<>();
if (currentToken instanceof RightBracket) {
consume();
return new JsonArray(list);
}
while (true) {
list.add(parseValue());
if (currentToken instanceof Comma) {
consume();
if (currentToken instanceof RightBracket) {
throw error("Trailing comma in array");
}
} else if (currentToken instanceof RightBracket) {
consume();
break;
} else {
throw error("Expected ',' or ']' after value");
}
}
return new JsonArray(list);
}
private void consume() {
currentToken = lexer.nextToken();
}
private JsonParseException error(String message) {
int pos = currentToken instanceof EndOfInput
? lexer.getColumn()
: tokenPosition(currentToken);
return new JsonParseException(
message + ", got " + tokenName(currentToken),
pos, lexer.getLine(), lexer.getColumn()
);
}
private int tokenPosition(Token t) {
return switch (t) {
case LeftBrace b -> b.position();
case RightBrace b -> b.position();
case LeftBracket b -> b.position();
case RightBracket b -> b.position();
case Colon c -> c.position();
case Comma c -> c.position();
case StringToken s -> s.position();
case NumberToken n -> n.position();
case TrueToken tok -> tok.position();
case FalseToken tok -> tok.position();
case NullToken tok -> tok.position();
case EndOfInput e -> e.position();
};
}
private String tokenName(Token t) {
return switch (t) {
case LeftBrace _ -> "'{'";
case RightBrace _ -> "'}'";
case LeftBracket _ -> "'['";
case RightBracket _ -> "']'";
case Colon _ -> "':'";
case Comma _ -> "','";
case StringToken s -> "string \"" + s.value() + "\"";
case NumberToken n -> "number " + n.raw();
case TrueToken _ -> "'true'";
case FalseToken _ -> "'false'";
case NullToken _ -> "'null'";
case EndOfInput _ -> "end of input";
};
}
} import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
final class JsonSerializer {
private final StringBuilder sb = new StringBuilder();
public String serialize(JsonValue value) {
appendValue(value);
return sb.toString();
}
private void appendValue(JsonValue value) {
switch (value) {
case JsonString s -> appendString(s.value());
case JsonNumber n -> appendNumber(n.value());
case JsonBoolean b -> sb.append(b.value());
case JsonNull _ -> sb.append("null");
case JsonArray a -> appendArray(a.elements());
case JsonObject o -> appendObject(o.fields());
}
}
private void appendString(String s) {
sb.append('"');
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '"' -> sb.append("\\\"");
case '\\' -> sb.append("\\\\");
case '\b' -> sb.append("\\b");
case '\f' -> sb.append("\\f");
case '\n' -> sb.append("\\n");
case '\r' -> sb.append("\\r");
case '\t' -> sb.append("\\t");
default -> {
if (c < 0x20 || c > 0x7E) {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
}
}
sb.append('"');
}
private void appendNumber(BigDecimal n) {
sb.append(n.toPlainString());
}
private void appendArray(List<JsonValue> elements) {
sb.append('[');
for (int i = 0; i < elements.size(); i++) {
if (i > 0) sb.append(',');
appendValue(elements.get(i));
}
sb.append(']');
}
private void appendObject(Map<String, JsonValue> fields) {
sb.append('{');
int i = 0;
for (Map.Entry<String, JsonValue> entry : fields.entrySet()) {
if (i++ > 0) sb.append(',');
appendString(entry.getKey());
sb.append(':');
appendValue(entry.getValue());
}
sb.append('}');
}
} import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
final class JsonSerializer {
private final StringBuilder sb = new StringBuilder();
public String serialize(JsonValue value) {
appendValue(value);
return sb.toString();
}
private void appendValue(JsonValue value) {
switch (value) {
case JsonString s -> appendString(s.value());
case JsonNumber n -> appendNumber(n.value());
case JsonBoolean b -> sb.append(b.value());
case JsonNull _ -> sb.append("null");
case JsonArray a -> appendArray(a.elements());
case JsonObject o -> appendObject(o.fields());
}
}
private void appendString(String s) {
sb.append('"');
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '"' -> sb.append("\\\"");
case '\\' -> sb.append("\\\\");
case '\b' -> sb.append("\\b");
case '\f' -> sb.append("\\f");
case '\n' -> sb.append("\\n");
case '\r' -> sb.append("\\r");
case '\t' -> sb.append("\\t");
default -> {
if (c < 0x20 || c > 0x7E) {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
}
}
sb.append('"');
}
private void appendNumber(BigDecimal n) {
sb.append(n.toPlainString());
}
private void appendArray(List<JsonValue> elements) {
sb.append('[');
for (int i = 0; i < elements.size(); i++) {
if (i > 0) sb.append(',');
appendValue(elements.get(i));
}
sb.append(']');
}
private void appendObject(Map<String, JsonValue> fields) {
sb.append('{');
int i = 0;
for (Map.Entry<String, JsonValue> entry : fields.entrySet()) {
if (i++ > 0) sb.append(',');
appendString(entry.getKey());
sb.append(':');
appendValue(entry.getValue());
}
sb.append('}');
}
} import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Objects;
sealed interface JsonValue permits
JsonString, JsonNumber, JsonBoolean, JsonNull, JsonArray, JsonObject {
default String asString() {
if (this instanceof JsonString s) return s.value();
throw new JsonTypeException("Expected string, got " + getClass().getSimpleName());
}
default BigDecimal asNumber() {
if (this instanceof JsonNumber n) return n.value();
throw new JsonTypeException("Expected number, got " + getClass().getSimpleName());
}
default boolean asBoolean() {
if (this instanceof JsonBoolean b) return b.value();
throw new JsonTypeException("Expected boolean, got " + getClass().getSimpleName());
}
default List<JsonValue> asArray() {
if (this instanceof JsonArray a) return a.elements();
throw new JsonTypeException("Expected array, got " + getClass().getSimpleName());
}
default Map<String, JsonValue> asObject() {
if (this instanceof JsonObject o) return o.fields();
throw new JsonTypeException("Expected object, got " + getClass().getSimpleName());
}
default boolean isNull() {
return this instanceof JsonNull;
}
}
final class JsonTypeException extends RuntimeException {
JsonTypeException(String message) { super(message); }
}
record JsonString(String value) implements JsonValue {
JsonString { Objects.requireNonNull(value, "value must not be null"); }
}
record JsonNumber(BigDecimal value) implements JsonValue {
JsonNumber { Objects.requireNonNull(value, "value must not be null"); }
}
record JsonBoolean(boolean value) implements JsonValue { }
final class JsonNull implements JsonValue {
private static final JsonNull INSTANCE = new JsonNull();
private JsonNull() { }
public static JsonNull getInstance() { return INSTANCE; }
@Override public String toString() { return "null"; }
}
record JsonArray(List<JsonValue> elements) implements JsonValue {
JsonArray { Objects.requireNonNull(elements, "elements must not be null"); }
}
record JsonObject(Map<String, JsonValue> fields) implements JsonValue {
JsonObject { Objects.requireNonNull(fields, "fields must not be null"); }
} import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Objects;
sealed interface JsonValue permits
JsonString, JsonNumber, JsonBoolean, JsonNull, JsonArray, JsonObject {
default String asString() {
if (this instanceof JsonString s) return s.value();
throw new JsonTypeException("Expected string, got " + getClass().getSimpleName());
}
default BigDecimal asNumber() {
if (this instanceof JsonNumber n) return n.value();
throw new JsonTypeException("Expected number, got " + getClass().getSimpleName());
}
default boolean asBoolean() {
if (this instanceof JsonBoolean b) return b.value();
throw new JsonTypeException("Expected boolean, got " + getClass().getSimpleName());
}
default List<JsonValue> asArray() {
if (this instanceof JsonArray a) return a.elements();
throw new JsonTypeException("Expected array, got " + getClass().getSimpleName());
}
default Map<String, JsonValue> asObject() {
if (this instanceof JsonObject o) return o.fields();
throw new JsonTypeException("Expected object, got " + getClass().getSimpleName());
}
default boolean isNull() {
return this instanceof JsonNull;
}
}
final class JsonTypeException extends RuntimeException {
JsonTypeException(String message) { super(message); }
}
record JsonString(String value) implements JsonValue {
JsonString { Objects.requireNonNull(value, "value must not be null"); }
}
record JsonNumber(BigDecimal value) implements JsonValue {
JsonNumber { Objects.requireNonNull(value, "value must not be null"); }
}
record JsonBoolean(boolean value) implements JsonValue { }
final class JsonNull implements JsonValue {
private static final JsonNull INSTANCE = new JsonNull();
private JsonNull() { }
public static JsonNull getInstance() { return INSTANCE; }
@Override public String toString() { return "null"; }
}
record JsonArray(List<JsonValue> elements) implements JsonValue {
JsonArray { Objects.requireNonNull(elements, "elements must not be null"); }
}
record JsonObject(Map<String, JsonValue> fields) implements JsonValue {
JsonObject { Objects.requireNonNull(fields, "fields must not be null"); }
} public class Main {
public static void main(String[] args) {
String json = """
{
"name": "Тимур",
"age": 30,
"active": true,
"tags": ["Java", "JSON", "Parser"],
"meta": {
"score": 9.87e-3,
"nullField": null
}
}
""";
JsonValue parsed = Json.parse(json);
System.out.println("Name: " + parsed.asObject().get("name").asString());
System.out.println("Round-trip:\n" + Json.stringify(parsed));
}
} public class Main {
public static void main(String[] args) {
String json = """
{
"name": "Тимур",
"age": 30,
"active": true,
"tags": ["Java", "JSON", "Parser"],
"meta": {
"score": 9.87e-3,
"nullField": null
}
}
""";
JsonValue parsed = Json.parse(json);
System.out.println("Name: " + parsed.asObject().get("name").asString());
System.out.println("Round-trip:\n" + Json.stringify(parsed));
}
} import java.math.BigDecimal;
sealed interface Token permits
LeftBrace, RightBrace, LeftBracket, RightBracket,
Colon, Comma, StringToken, NumberToken,
TrueToken, FalseToken, NullToken, EndOfInput { }
record LeftBrace(int position) implements Token { }
record RightBrace(int position) implements Token { }
record LeftBracket(int position) implements Token { }
record RightBracket(int position) implements Token { }
record Colon(int position) implements Token { }
record Comma(int position) implements Token { }
record StringToken(String value, int position) implements Token { }
record NumberToken(BigDecimal value, String raw, int position) implements Token { }
record TrueToken(int position) implements Token { }
record FalseToken(int position) implements Token { }
record NullToken(int position) implements Token { }
record EndOfInput(int position) implements Token { } import java.math.BigDecimal;
sealed interface Token permits
LeftBrace, RightBrace, LeftBracket, RightBracket,
Colon, Comma, StringToken, NumberToken,
TrueToken, FalseToken, NullToken, EndOfInput { }
record LeftBrace(int position) implements Token { }
record RightBrace(int position) implements Token { }
record LeftBracket(int position) implements Token { }
record RightBracket(int position) implements Token { }
record Colon(int position) implements Token { }
record Comma(int position) implements Token { }
record StringToken(String value, int position) implements Token { }
record NumberToken(BigDecimal value, String raw, int position) implements Token { }
record TrueToken(int position) implements Token { }
record FalseToken(int position) implements Token { }
record NullToken(int position) implements Token { }
record EndOfInput(int position) implements Token { }