JSON Formatter — Java source
Beautify, minify and validate JSON instantly. Paste, format, copy - all in your browser. Share a link to your exact input.
This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// json-formatter — parse, pretty-print (2-space indent), and minify.
//
// Language: Java (Java 17, standard library only — the JDK ships no public
// JSON API; the ecosystem equivalents are Jackson or Gson)
// Source: CosmoDev polyglot showcase port of the `json-formatter` tool,
// ported from src/tools/JsonFormatter.tsx (the canonical
// TypeScript island). In a real project this class lives in
// JsonFormatter.java; it is named java.java for the polyglot
// layout.
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Mirroring the Rust port, this file includes a small hand-written
// recursive-descent parser and serializer. Object members are stored in a
// List<Member>, which preserves source insertion order just like a
// JavaScript engine.
//
// Parity caveats: numbers are held as double (matching JS Number), so
// integers beyond 2^53 lose precision, and Double#toString keeps a
// trailing ".0" / switches to 'E' notation at different thresholds than
// ECMAScript's Number::toString (integral values below 1e15 are printed as
// plain integers to match the common cases). String content is iterated as
// UTF-16 chars, so lone surrogates in raw (non-escaped) input pass through
// unchanged.
import java.util.ArrayList;
import java.util.List;
public final class JsonFormatter {
private JsonFormatter() {}
/** Selects the output shape. */
public enum Mode { BEAUTIFY, MINIFY }
/** Mirrors the TypeScript { output, error } shape; error == null means
* success or empty input. */
public record FormatResult(String output, String error) {}
/** A parsed JSON value. Object members preserve source insertion order. */
public sealed interface Json permits Null, Bool, Num, Str, Arr, Obj {}
public record Null() implements Json {}
public record Bool(boolean value) implements Json {}
public record Num(double value) implements Json {}
public record Str(String value) implements Json {}
public record Arr(List<Json> items) implements Json {}
public record Obj(List<Member> members) implements Json {}
public record Member(String key, Json value) {}
/** A parse failure: byte offset + message (checked, like
* java.text.ParseException). */
public static final class ParseException extends Exception {
public final int offset;
ParseException(int offset, String message) {
super(message);
this.offset = offset;
}
}
/**
* Parse and re-emit a JSON document the way the live island does.
* {@code indent} is the number of spaces per nesting level for
* {@code BEAUTIFY} (pass 2 to match the UI). Empty/whitespace-only
* input yields empty output, no error.
*/
public static FormatResult formatJson(String input, Mode mode, int indent) {
String raw = input.strip();
if (raw.isEmpty()) return new FormatResult("", null);
Json value;
try {
value = new Parser(raw).parseDocument();
} catch (ParseException e) {
return new FormatResult("", e.getMessage() + " at byte " + e.offset);
}
String pad = " ".repeat(indent);
StringBuilder out = new StringBuilder();
if (mode == Mode.MINIFY) {
writeCompact(value, out);
} else {
writePretty(value, out, 0, pad);
}
return new FormatResult(out.toString(), null);
}
// -----------------------------------------------------------------
// Parser — a minimal recursive-descent JSON parser.
// -----------------------------------------------------------------
static final class Parser {
private final String text;
private int pos;
Parser(String text) { this.text = text; }
ParseException fail(String message) { return new ParseException(pos, message); }
private char peek() { return pos < text.length() ? text.charAt(pos) : '\0'; }
private static boolean isDigit(char c) { return c >= '0' && c <= '9'; }
private void skipWs() {
while (peek() == ' ' || peek() == '\t' || peek() == '\n' || peek() == '\r') pos++;
}
/** Match a literal keyword at the current position; on success advance. */
private boolean matchKw(String kw) {
if (text.regionMatches(pos, kw, 0, kw.length())) {
pos += kw.length();
return true;
}
return false;
}
Json parseDocument() throws ParseException {
skipWs();
Json value = parseValue();
skipWs();
if (pos < text.length()) throw fail("unexpected trailing characters");
return value;
}
private Json parseValue() throws ParseException {
char c = peek();
if (c == '-' || isDigit(c)) return parseNumber();
switch (c) {
case '{': return parseObject();
case '[': return parseArray();
case '"': return new Str(parseString());
case 't': case 'f': return parseBool();
case 'n': return parseNull();
default: throw fail("unexpected token");
}
}
private Json parseObject() throws ParseException {
pos++; // consume '{'
skipWs();
List<Member> members = new ArrayList<>();
if (peek() == '}') { pos++; return new Obj(members); }
while (true) {
skipWs();
if (peek() != '"') throw fail("expected string key");
String key = parseString();
skipWs();
if (peek() != ':') throw fail("expected ':' after key");
pos++;
skipWs();
members.add(new Member(key, parseValue()));
skipWs();
if (peek() == ',') { pos++; continue; }
if (peek() == '}') { pos++; return new Obj(members); }
throw fail("expected ',' or '}'");
}
}
private Json parseArray() throws ParseException {
pos++; // consume '['
skipWs();
List<Json> items = new ArrayList<>();
if (peek() == ']') { pos++; return new Arr(items); }
while (true) {
skipWs();
items.add(parseValue());
skipWs();
if (peek() == ',') { pos++; continue; }
if (peek() == ']') { pos++; return new Arr(items); }
throw fail("expected ',' or ']'");
}
}
private String parseString() throws ParseException {
pos++; // opening '"'
StringBuilder s = new StringBuilder();
while (true) {
char c = peek();
if (c == '\0') throw fail("unterminated string");
if (c == '"') { pos++; return s.toString(); }
if (c == '\\') {
pos++;
parseEscape(s);
} else if (c < 0x20) {
throw fail("unescaped control character in string");
} else {
s.append(c);
pos++;
}
}
}
private void parseEscape(StringBuilder s) throws ParseException {
char c = peek();
switch (c) {
case '"' -> { pos++; s.append('"'); }
case '\\' -> { pos++; s.append('\\'); }
case '/' -> { pos++; s.append('/'); }
case 'b' -> { pos++; s.append('\b'); }
case 'f' -> { pos++; s.append('\f'); }
case 'n' -> { pos++; s.append('\n'); }
case 'r' -> { pos++; s.append('\r'); }
case 't' -> { pos++; s.append('\t'); }
case 'u' -> { pos++; parseUnicodeEscape(s); }
default -> throw fail("invalid escape sequence");
}
}
// Enforce correct UTF-16 surrogate pairing for \\u escapes.
// (Doubled backslash: javac lexes unicode escapes even in comments.)
private void parseUnicodeEscape(StringBuilder s) throws ParseException {
int cp = parseHex4();
if (cp >= 0xD800 && cp <= 0xDBFF) {
if (peek() != '\\') throw fail("dangling high surrogate");
pos++;
if (peek() != 'u') throw fail("expected '\\u' for surrogate pair");
pos++;
int lo = parseHex4();
if (lo < 0xDC00 || lo > 0xDFFF)
throw fail("invalid low surrogate after high surrogate");
s.appendCodePoint(0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00));
} else if (cp >= 0xDC00 && cp <= 0xDFFF) {
throw fail("unexpected low surrogate");
} else {
s.appendCodePoint(cp);
}
}
// Read exactly four hexadecimal digits following a \\u escape.
private int parseHex4() throws ParseException {
int value = 0;
for (int i = 0; i < 4; i++) {
char c = peek();
int d;
if (c >= '0' && c <= '9') d = c - '0';
else if (c >= 'a' && c <= 'f') d = c - 'a' + 10;
else if (c >= 'A' && c <= 'F') d = c - 'A' + 10;
else throw fail("invalid hex digit in '\\u' escape");
value = value * 16 + d;
pos++;
}
return value;
}
private Json parseNumber() throws ParseException {
int start = pos;
if (peek() == '-') pos++;
if (peek() == '0') {
pos++;
} else if (peek() >= '1' && peek() <= '9') {
while (isDigit(peek())) pos++;
} else {
throw new ParseException(start, "invalid number");
}
if (peek() == '.') {
pos++;
if (!isDigit(peek())) throw fail("expected digit after decimal point");
while (isDigit(peek())) pos++;
}
if (peek() == 'e' || peek() == 'E') {
pos++;
if (peek() == '+' || peek() == '-') pos++;
if (!isDigit(peek())) throw fail("expected digit in exponent");
while (isDigit(peek())) pos++;
}
try {
return new Num(Double.parseDouble(text.substring(start, pos)));
} catch (NumberFormatException e) {
throw fail("invalid number");
}
}
private Json parseBool() throws ParseException {
if (matchKw("true")) return new Bool(true);
if (matchKw("false")) return new Bool(false);
throw fail("invalid literal");
}
private Json parseNull() throws ParseException {
if (matchKw("null")) return new Null();
throw fail("invalid literal");
}
}
// -----------------------------------------------------------------
// Serializer.
// -----------------------------------------------------------------
private static void pushIndent(StringBuilder out, int depth, String pad) {
out.append(pad.repeat(depth));
}
/** Emit a JSON string literal, escaping per RFC 8259. Like
* JSON.stringify it leaves '/' unescaped and emits control characters
* below U+0020 as \\uXXXX. */
private static void writeJsonString(String value, StringBuilder out) {
out.append('"');
value.codePoints().forEach(cp -> {
switch (cp) {
case '"' -> out.append("\\\"");
case '\\' -> out.append("\\\\");
case '\n' -> out.append("\\n");
case '\r' -> out.append("\\r");
case '\t' -> out.append("\\t");
case '\b' -> out.append("\\b");
case '\f' -> out.append("\\f");
default -> {
if (cp < 0x20) out.append(String.format("\\u%04x", cp));
else out.appendCodePoint(cp);
}
}
});
out.append('"');
}
/** Print a double the way JavaScript most often renders it: integral
* values below 1e15 without a trailing ".0", everything else via
* Double#toString. */
private static void writeNumber(double v, StringBuilder out) {
if (v == Math.rint(v) && Math.abs(v) < 1e15) {
out.append((long) v);
} else {
out.append(v);
}
}
private static void writeCompact(Json value, StringBuilder out) {
if (value instanceof Null) {
out.append("null");
} else if (value instanceof Bool b) {
out.append(b.value() ? "true" : "false");
} else if (value instanceof Num n) {
writeNumber(n.value(), out);
} else if (value instanceof Str s) {
writeJsonString(s.value(), out);
} else if (value instanceof Arr a) {
out.append('[');
List<Json> items = a.items();
for (int i = 0; i < items.size(); i++) {
if (i > 0) out.append(',');
writeCompact(items.get(i), out);
}
out.append(']');
} else if (value instanceof Obj o) {
out.append('{');
List<Member> members = o.members();
for (int i = 0; i < members.size(); i++) {
if (i > 0) out.append(',');
writeJsonString(members.get(i).key(), out);
out.append(':');
writeCompact(members.get(i).value(), out);
}
out.append('}');
}
}
private static void writePretty(Json value, StringBuilder out, int depth, String pad) {
if (value instanceof Arr a && !a.items().isEmpty()) {
out.append('[');
List<Json> items = a.items();
for (int i = 0; i < items.size(); i++) {
if (i > 0) out.append(',');
out.append('\n');
pushIndent(out, depth + 1, pad);
writePretty(items.get(i), out, depth + 1, pad);
}
out.append('\n');
pushIndent(out, depth, pad);
out.append(']');
} else if (value instanceof Obj o && !o.members().isEmpty()) {
out.append('{');
List<Member> members = o.members();
for (int i = 0; i < members.size(); i++) {
if (i > 0) out.append(',');
out.append('\n');
pushIndent(out, depth + 1, pad);
writeJsonString(members.get(i).key(), out);
out.append(": ");
writePretty(members.get(i).value(), out, depth + 1, pad);
}
out.append('\n');
pushIndent(out, depth, pad);
out.append('}');
} else {
// Scalars and empty containers render identically in both modes.
writeCompact(value, out);
}
}
// -----------------------------------------------------------------
// Demo.
// -----------------------------------------------------------------
public static void main(String[] args) {
String sample = "{\"name\":\"CosmoDev\",\"tools\":[\"json\",\"base64\"],"
+ "\"free\":true,\"nested\":{\"ok\":1}}";
FormatResult result = formatJson(sample, Mode.BEAUTIFY, 2);
System.out.println(result.error() != null ? result.error() : result.output());
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →