Last active
October 3, 2025 12:50
-
-
Save faceless2/9410213ae365da463eef93776343166c to your computer and use it in GitHub Desktop.
Parse JSON in Java with a single 100-line method and no dependencies.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import java.util.*; | |
| import java.nio.CharBuffer; | |
| import java.nio.BufferUnderflowException; | |
| /** | |
| * A single-method JSON parser in 100 lines of Java. | |
| * | |
| * Returns a String, Boolean, Integer, Long, Double, null, a List of those, or a Map<String,Object> of those. | |
| * Intended to parse input which is expected to be valid. If you want 100% correct error handling, go elsewhere. | |
| * Does not exacly match the JSON parsing rules for numbers. | |
| * | |
| * To parse a String: parseJson(CharBuffer.wrap(string)); | |
| */ | |
| public static Object parseJson(CharBuffer in) { | |
| int tell = in.position(); | |
| try { | |
| char c; | |
| while ((c=in.get()) == ' ' || c == '\n' || c == '\r' || c == '\t') { | |
| tell++; | |
| } | |
| Object out; | |
| if (c == '{') { | |
| Map<String,Object> m = new LinkedHashMap<String,Object>(); | |
| while ((c=in.get()) == ' ' || c == '\n' || c == '\r' || c == '\t'); | |
| if (c != '}') { | |
| in.position(in.position() - 1); | |
| do { | |
| String key = (String)parseJson(in); | |
| while ((c=in.get()) == ' ' || c == '\n' || c == '\r' || c == '\t'); | |
| if (c == ':') { | |
| m.put((String)key, parseJson(in)); | |
| tell = in.position(); | |
| } else { | |
| throw new UnsupportedOperationException("expecting colon"); | |
| } | |
| while ((c=in.get()) == ' ' || c == '\n' || c == '\r' || c == '\t'); | |
| if (c != ',' && c != '}') { | |
| throw new UnsupportedOperationException("expecting comma or end-map"); | |
| } | |
| } while (c != '}'); | |
| } | |
| out = m; | |
| } else if (c == '[') { | |
| List<Object> l = new ArrayList<Object>(); | |
| while ((c=in.get()) == ' ' || c == '\n' || c == '\r' || c == '\t'); | |
| if (c != ']') { | |
| in.position(in.position() - 1); | |
| do { | |
| l.add(parseJson(in)); | |
| tell = in.position(); | |
| while ((c=in.get()) == ' ' || c == '\n' || c == '\r' || c == '\t'); | |
| if (c != ',' && c != ']') { | |
| throw new UnsupportedOperationException("expecting comma or end-list"); | |
| } | |
| } while (c != ']'); | |
| } | |
| out = l; | |
| } else if (c == '"') { | |
| StringBuilder sb = new StringBuilder(); | |
| while ((c=in.get()) != '"') { | |
| if (c == '\\') { | |
| c = in.get(); | |
| switch (c) { | |
| case 'n': c = '\n'; break; | |
| case 'r': c = '\r'; break; | |
| case 't': c = '\t'; break; | |
| case 'b': c = '\b'; break; | |
| case 'f': c = '\f'; break; | |
| case 'u': c = (char)Integer.parseInt(in.subSequence(0, 4).toString(), 16); in.position(in.position() + 4); break; | |
| } | |
| } | |
| sb.append(c); | |
| } | |
| out = sb.toString(); | |
| } else if (c == 't' && in.get() == 'r' && in.get() == 'u' && in.get() == 'e') { | |
| out = Boolean.TRUE; | |
| } else if (c == 'f' && in.get() == 'a' && in.get() == 'l' && in.get() == 's' && in.get() == 'e') { | |
| out = Boolean.FALSE; | |
| } else if (c == 'n' && in.get() == 'u' && in.get() == 'l' && in.get() == 'l') { | |
| out = null; | |
| } else if (c == '-' || (c >= '0' && c <= '9')) { | |
| StringBuilder sb = new StringBuilder(); | |
| sb.append(c); | |
| while (in.hasRemaining()) { | |
| if ((c=in.get()) == '.' || c == 'e' || c == 'E' || (c >= '0' && c <= '9')) { | |
| sb.append(c); | |
| } else { | |
| in.position(in.position() - 1); | |
| break; | |
| } | |
| } | |
| String s = sb.toString(); | |
| try { | |
| Long l = Long.parseLong(s); | |
| if (l.longValue() == l.intValue()) { // This can't be done with a ternary due to unboxing confusion | |
| out = Integer.valueOf(l.intValue()); | |
| } else { | |
| out = l; | |
| } | |
| } catch (Exception e) { | |
| try { | |
| out = Double.parseDouble(s); | |
| } catch (Exception e2) { | |
| throw new UnsupportedOperationException("invalid number: " + s); | |
| } | |
| } | |
| } else { | |
| throw new UnsupportedOperationException("invalid " + (c >= ' ' && c < 0x80 ? "'" + ((char)c) + "'" : "U+" + Integer.toHexString(c))); | |
| } | |
| return out; | |
| } catch (BufferUnderflowException e) { | |
| throw (IllegalArgumentException)new IllegalArgumentException("Parse failed: unexpected EOF").initCause(e); | |
| } catch (ClassCastException e) { | |
| in.position(tell); | |
| throw new IllegalArgumentException("Parse failed at " + in.position() + ": expected string"); | |
| } catch (UnsupportedOperationException e) { | |
| in.position(tell); | |
| throw new IllegalArgumentException("Parse failed at " + in.position() + ": " + e.getMessage()); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment