/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.dashbuilder.json; import java.util.Collections; import java.util.HashSet; import java.util.Set; /** * Direct port of json2.js at http://www.json.org/json2.js to GWT. */ public class JsonUtil { private static class StringifyJsonVisitor extends JsonVisitor { private String indentLevel; private Set<JsonValue> visited; private final String indent; private final StringBuilder sb; private final boolean pretty; public StringifyJsonVisitor(String indent, StringBuilder sb, boolean pretty) { this.indent = indent; this.sb = sb; this.pretty = pretty; indentLevel = ""; visited = new HashSet<JsonValue>(); } @Override public void endVisit(JsonArray array, JsonContext ctx) { if (pretty) { indentLevel = indentLevel .substring(0, indentLevel.length() - indent.length()); sb.append('\n'); sb.append(indentLevel); } sb.append("]"); visited.remove(array); } @Override public void endVisit(JsonObject object, JsonContext ctx) { if (pretty) { indentLevel = indentLevel .substring(0, indentLevel.length() - indent.length()); sb.append('\n'); sb.append(indentLevel); } sb.append("}"); visited.remove(object); assert !visited.contains(object); } @Override public void visit(double number, JsonContext ctx) { sb.append(Double.isInfinite(number) ? "null" : format(number)); } @Override public void visit(String string, JsonContext ctx) { sb.append(quote(string)); } @Override public void visit(boolean bool, JsonContext ctx) { sb.append(bool); } @Override public boolean visit(JsonArray array, JsonContext ctx) { checkCycle(array); sb.append("["); if (pretty) { sb.append('\n'); indentLevel += indent; sb.append(indentLevel); } return true; } @Override public boolean visit(JsonObject object, JsonContext ctx) { checkCycle(object); sb.append("{"); if (pretty) { sb.append('\n'); indentLevel += indent; sb.append(indentLevel); } return true; } @Override public boolean visitIndex(int index, JsonContext ctx) { commaIfNotFirst(ctx); return true; } @Override public boolean visitKey(String key, JsonContext ctx) { if ("".equals(key)) { return true; } commaIfNotFirst(ctx); sb.append(quote(key) + ":"); if (pretty) { sb.append(' '); } return true; } @Override public void visitNull(JsonContext ctx) { sb.append("null"); } private void checkCycle(JsonValue value) { if (visited.contains(value)) { throw new JsonException("Cycled detected during stringify"); } else { visited.add(value); } } private void commaIfNotFirst(JsonContext ctx) { if (!ctx.isFirst()) { sb.append(","); if (pretty) { sb.append('\n'); sb.append(indentLevel); } } } private String format(double number) { String n = String.valueOf(number); if (n.endsWith(".0")) { n = n.substring(0, n.length() - 2); } return n; } } /** * Convert special control characters into unicode escape format. */ public static String escapeControlChars(String text) { StringBuilder toReturn = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); if (isControlChar(c)) { toReturn.append(escapeStringAsUnicode(String.valueOf(c))); } else { toReturn.append(c); } } return toReturn.toString(); } public static <T extends JsonValue> T parse(String json) throws JsonException { return Json.instance().parse(json); } /** * Safely escape an arbitrary string as a JSON string literal. */ public static String quote(String value) { StringBuilder toReturn = new StringBuilder("\""); for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); String toAppend = String.valueOf(c); switch (c) { case '\b': toAppend = "\\b"; break; case '\t': toAppend = "\\t"; break; case '\n': toAppend = "\\n"; break; case '\f': toAppend = "\\f"; break; case '\r': toAppend = "\\r"; break; case '"': toAppend = "\\\""; break; case '\\': toAppend = "\\\\"; break; default: if (isControlChar(c)) { toAppend = escapeStringAsUnicode(String.valueOf(c)); } } toReturn.append(toAppend); } toReturn.append("\""); return toReturn.toString(); } /** * Converts a Json Object to Json format. * * @param jsonValue json object to stringify * @return json formatted string */ public static String stringify(JsonValue jsonValue) { return stringify(jsonValue, 0); } /** * Converts a JSO to Json format. * * @param jsonValue json object to stringify * @param spaces number of spaces to indent in pretty print mode * @return json formatted string */ public static String stringify(JsonValue jsonValue, int spaces) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < spaces; i++) { sb.append(' '); } return stringify(jsonValue, sb.toString()); } /** * Converts a Json object to Json formatted String. * * @param jsonValue json object to stringify * @param indent optional indention prefix for pretty printing * @return json formatted string */ public static String stringify(JsonValue jsonValue, final String indent) { final StringBuilder sb = new StringBuilder(); final boolean isPretty = indent != null && !"".equals(indent); new StringifyJsonVisitor(indent, sb, isPretty).accept(jsonValue); return sb.toString(); } /** * Turn a single unicode character into a 32-bit unicode hex literal. */ private static String escapeStringAsUnicode(String match) { String hexValue = Integer.toString(match.charAt(0), 16); hexValue = hexValue.length() > 4 ? hexValue.substring(hexValue.length() - 4) : hexValue; return "\\u0000" + hexValue; } private static boolean isControlChar(char c) { return (c >= 0x00 && c <= 0x1f) || (c >= 0x7f && c <= 0x9f) || c == '\u00ad' || c == '\u070f' || c == '\u17b4' || c == '\u17b5' || c == '\ufeff' || (c >= '\u0600' && c <= '\u0604') || (c >= '\u200c' && c <= '\u200f') || (c >= '\u2028' && c <= '\u202f') || (c >= '\u2060' && c <= '\u206f') || (c >= '\ufff0' && c <= '\uffff'); } }