package com.lanremote.watch; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 极简 JSON 编解码器。 * * 刻意不依赖 org.json / Gson:这样协议层是**纯 Java**, * 既能在安卓上跑,也能在桌面 JVM 上做跨语言联调测试。 */ public final class Json { private Json() { } // ------------------------------------------------------------------ // 构造辅助 // ------------------------------------------------------------------ public static Map obj() { return new LinkedHashMap(); } public static Map obj(Object... kv) { Map map = new LinkedHashMap(); for (int i = 0; i + 1 < kv.length; i += 2) { map.put(String.valueOf(kv[i]), kv[i + 1]); } return map; } public static String str(Map map, String key, String fallback) { Object value = map == null ? null : map.get(key); return value == null ? fallback : String.valueOf(value); } public static int integer(Map map, String key, int fallback) { Object value = map == null ? null : map.get(key); if (value instanceof Number) { return ((Number) value).intValue(); } if (value == null) { return fallback; } try { return (int) Double.parseDouble(String.valueOf(value)); } catch (NumberFormatException e) { return fallback; } } public static boolean bool(Map map, String key, boolean fallback) { Object value = map == null ? null : map.get(key); if (value instanceof Boolean) { return (Boolean) value; } if (value == null) { return fallback; } return Boolean.parseBoolean(String.valueOf(value)); } @SuppressWarnings("unchecked") /** * 取一个字符串数组。元素不是字符串的会被跳过。 * * 中转会把被控端上报的局域网 IP 列表放在这里(lanIps), * 手表用它判断能不能走局域网直连。 */ public static List stringList(Map map, String key) { List out = new java.util.ArrayList(); List raw = list(map, key); if (raw == null) { return out; } for (Object item : raw) { if (item instanceof String) { String text = ((String) item).trim(); if (!text.isEmpty()) { out.add(text); } } } return out; } public static List list(Map map, String key) { Object value = map == null ? null : map.get(key); if (value instanceof List) { return (List) value; } return new ArrayList(); } @SuppressWarnings("unchecked") public static Map asMap(Object value) { return value instanceof Map ? (Map) value : null; } // ------------------------------------------------------------------ // 编码 // ------------------------------------------------------------------ /** * 把字符串转义成能安全塞进 JSON 字符串字面量的形式。 * 只处理引号、反斜杠和控制字符 —— 中文不用转,JSON 本身是 UTF-8。 */ public static String escape(String text) { if (text == null) { return ""; } StringBuilder out = new StringBuilder(text.length() + 16); for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); switch (ch) { case '"': out.append("\\\""); break; case '\\': out.append("\\\\"); break; case '\n': out.append("\\n"); break; case '\r': out.append("\\r"); break; case '\t': out.append("\\t"); break; default: if (ch < 0x20) { out.append(String.format("\\u%04x", (int) ch)); } else { out.append(ch); } break; } } return out.toString(); } public static String write(Object value) { StringBuilder sb = new StringBuilder(256); writeValue(sb, value); return sb.toString(); } private static void writeValue(StringBuilder sb, Object value) { if (value == null) { sb.append("null"); } else if (value instanceof String) { writeString(sb, (String) value); } else if (value instanceof Boolean) { sb.append(((Boolean) value) ? "true" : "false"); } else if (value instanceof Double || value instanceof Float) { double d = ((Number) value).doubleValue(); if (d == Math.rint(d) && !Double.isInfinite(d)) { sb.append((long) d); } else { sb.append(d); } } else if (value instanceof Number) { sb.append(((Number) value).longValue()); } else if (value instanceof Map) { writeObject(sb, (Map) value); } else if (value instanceof Iterable) { writeArray(sb, (Iterable) value); } else if (value instanceof byte[]) { writeString(sb, new String((byte[]) value, java.nio.charset.Charset.forName("UTF-8"))); } else { writeString(sb, String.valueOf(value)); } } private static void writeObject(StringBuilder sb, Map map) { sb.append('{'); boolean first = true; for (Map.Entry entry : map.entrySet()) { if (!first) { sb.append(','); } first = false; writeString(sb, String.valueOf(entry.getKey())); sb.append(':'); writeValue(sb, entry.getValue()); } sb.append('}'); } private static void writeArray(StringBuilder sb, Iterable items) { sb.append('['); boolean first = true; for (Object item : items) { if (!first) { sb.append(','); } first = false; writeValue(sb, item); } sb.append(']'); } private static void writeString(StringBuilder sb, String text) { sb.append('"'); for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); switch (c) { case '"': sb.append("\\\""); break; case '\\': sb.append("\\\\"); break; case '\n': sb.append("\\n"); break; case '\r': sb.append("\\r"); break; case '\t': sb.append("\\t"); break; case '\b': sb.append("\\b"); break; case '\f': sb.append("\\f"); break; default: if (c < 0x20) { sb.append(String.format("\\u%04x", (int) c)); } else { sb.append(c); } } } sb.append('"'); } // ------------------------------------------------------------------ // 解码 // ------------------------------------------------------------------ public static Object parse(String text) { Parser parser = new Parser(text); parser.skipWhitespace(); Object value = parser.readValue(); parser.skipWhitespace(); if (!parser.atEnd()) { throw new IllegalArgumentException("JSON 尾部存在多余内容"); } return value; } public static Map parseObject(String text) { Object value = parse(text); Map map = asMap(value); if (map == null) { throw new IllegalArgumentException("期望 JSON 对象"); } return map; } private static final class Parser { /** 最大嵌套深度。不加限制的话,一行 "[[[[…" 就能把栈撑爆, * 而 UDP 发现的入口是局域网里任何主机都能打的,等于远程 DoS。 */ private static final int MAX_DEPTH = 64; private final String src; private int pos; private int depth; Parser(String src) { this.src = src; } boolean atEnd() { return pos >= src.length(); } void skipWhitespace() { while (pos < src.length()) { char c = src.charAt(pos); if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { pos++; } else { break; } } } Object readValue() { if (atEnd()) { throw new IllegalArgumentException("JSON 意外结束"); } if (depth > MAX_DEPTH) { throw new IllegalArgumentException("JSON 嵌套太深"); } char c = src.charAt(pos); switch (c) { case '{': return readObject(); case '[': return readArray(); case '"': return readString(); case 't': expect("true"); return Boolean.TRUE; case 'f': expect("false"); return Boolean.FALSE; case 'n': expect("null"); return null; default: return readNumber(); } } private void expect(String literal) { if (!src.startsWith(literal, pos)) { throw new IllegalArgumentException("位置 " + pos + " 处 JSON 非法"); } pos += literal.length(); } private Map readObject() { if (depth >= MAX_DEPTH) { throw new IllegalArgumentException("JSON 嵌套太深"); } depth++; try { return readObjectInner(); } finally { // 必须配对减去!只加不减会让计数器单调递增, // 于是"回复里有 65 个对象"就被误判成嵌套太深, // 而调用方会静默丢弃这条回复 -> 表现为"连不上电脑"。 depth--; } } private Map readObjectInner() { Map map = new LinkedHashMap(); pos++; // '{' skipWhitespace(); if (!atEnd() && src.charAt(pos) == '}') { pos++; return map; } while (true) { skipWhitespace(); String key = readString(); skipWhitespace(); if (atEnd() || src.charAt(pos) != ':') { throw new IllegalArgumentException("对象缺少 ':',位置 " + pos); } pos++; skipWhitespace(); map.put(key, readValue()); skipWhitespace(); if (atEnd()) { throw new IllegalArgumentException("对象未闭合"); } char c = src.charAt(pos); if (c == ',') { pos++; continue; } if (c == '}') { pos++; return map; } throw new IllegalArgumentException("对象分隔符非法,位置 " + pos); } } private List readArray() { if (depth >= MAX_DEPTH) { throw new IllegalArgumentException("JSON 嵌套太深"); } depth++; try { return readArrayInner(); } finally { depth--; } } private List readArrayInner() { List items = new ArrayList(); pos++; // '[' skipWhitespace(); if (!atEnd() && src.charAt(pos) == ']') { pos++; return items; } while (true) { skipWhitespace(); items.add(readValue()); skipWhitespace(); if (atEnd()) { throw new IllegalArgumentException("数组未闭合"); } char c = src.charAt(pos); if (c == ',') { pos++; continue; } if (c == ']') { pos++; return items; } throw new IllegalArgumentException("数组分隔符非法,位置 " + pos); } } private String readString() { if (atEnd() || src.charAt(pos) != '"') { throw new IllegalArgumentException("期望字符串,位置 " + pos); } pos++; StringBuilder sb = new StringBuilder(64); while (true) { if (atEnd()) { throw new IllegalArgumentException("字符串未闭合"); } char c = src.charAt(pos++); if (c == '"') { return sb.toString(); } if (c != '\\') { sb.append(c); continue; } if (atEnd()) { throw new IllegalArgumentException("转义字符未完成"); } char esc = src.charAt(pos++); switch (esc) { case '"': sb.append('"'); break; case '\\': sb.append('\\'); break; case '/': sb.append('/'); break; case 'n': sb.append('\n'); break; case 'r': sb.append('\r'); break; case 't': sb.append('\t'); break; case 'b': sb.append('\b'); break; case 'f': sb.append('\f'); break; case 'u': if (pos + 4 > src.length()) { throw new IllegalArgumentException("\\u 转义不完整"); } sb.append((char) Integer.parseInt(src.substring(pos, pos + 4), 16)); pos += 4; break; default: throw new IllegalArgumentException("未知转义 \\" + esc); } } } private Object readNumber() { int start = pos; while (pos < src.length()) { char c = src.charAt(pos); if ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.' || c == 'e' || c == 'E') { pos++; } else { break; } } String token = src.substring(start, pos); if (token.isEmpty()) { throw new IllegalArgumentException("非法数字,位置 " + start); } if (token.indexOf('.') < 0 && token.indexOf('e') < 0 && token.indexOf('E') < 0) { try { return Long.valueOf(token); } catch (NumberFormatException ignored) { // 落到 double } } return Double.valueOf(token); } } }