/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 groovy.json; import groovy.json.internal.CharBuf; import groovy.json.internal.Chr; import groovy.lang.Closure; import groovy.util.Expando; import java.io.StringReader; import java.net.URL; import java.util.*; /** * Class responsible for the actual String serialization of the possible values of a JSON structure. * This class can also be used as a category, so as to add <code>toJson()</code> methods to various types. * <p> * This class does not provide the ability to customize the resulting output. A {@link JsonGenerator} * can be used if the ability to alter the resulting output is required. * * @author Guillaume Laforge * @author Roshan Dawrani * @author Andrey Bloschetsov * @author Rick Hightower * @author Graeme Rocher * * @see JsonGenerator * @since 1.8.0 */ public class JsonOutput { static final char OPEN_BRACKET = '['; static final char CLOSE_BRACKET = ']'; static final char OPEN_BRACE = '{'; static final char CLOSE_BRACE = '}'; static final char COLON = ':'; static final char COMMA = ','; static final char SPACE = ' '; static final char NEW_LINE = '\n'; static final char QUOTE = '"'; static final char[] EMPTY_STRING_CHARS = Chr.array(QUOTE, QUOTE); static final char[] EMPTY_MAP_CHARS = {OPEN_BRACE, CLOSE_BRACE}; static final char[] EMPTY_LIST_CHARS = {OPEN_BRACKET, CLOSE_BRACKET}; /* package-private for use in builders */ static final JsonGenerator DEFAULT_GENERATOR = new DefaultJsonGenerator(new JsonGenerator.Options()); /** * @return "true" or "false" for a boolean value */ public static String toJson(Boolean bool) { return DEFAULT_GENERATOR.toJson(bool); } /** * @return a string representation for a number * @throws JsonException if the number is infinite or not a number. */ public static String toJson(Number n) { return DEFAULT_GENERATOR.toJson(n); } /** * @return a JSON string representation of the character */ public static String toJson(Character c) { return DEFAULT_GENERATOR.toJson(c); } /** * @return a properly encoded string with escape sequences */ public static String toJson(String s) { return DEFAULT_GENERATOR.toJson(s); } /** * Format a date that is parseable from JavaScript, according to ISO-8601. * * @param date the date to format to a JSON string * @return a formatted date in the form of a string */ public static String toJson(Date date) { return DEFAULT_GENERATOR.toJson(date); } /** * Format a calendar instance that is parseable from JavaScript, according to ISO-8601. * * @param cal the calendar to format to a JSON string * @return a formatted date in the form of a string */ public static String toJson(Calendar cal) { return DEFAULT_GENERATOR.toJson(cal); } /** * @return the string representation of an uuid */ public static String toJson(UUID uuid) { return DEFAULT_GENERATOR.toJson(uuid); } /** * @return the string representation of the URL */ public static String toJson(URL url) { return DEFAULT_GENERATOR.toJson(url); } /** * @return an object representation of a closure */ public static String toJson(Closure closure) { return DEFAULT_GENERATOR.toJson(closure); } /** * @return an object representation of an Expando */ public static String toJson(Expando expando) { return DEFAULT_GENERATOR.toJson(expando); } /** * @return "null" for a null value, or a JSON array representation for a collection, array, iterator or enumeration, * or representation for other object. */ public static String toJson(Object object) { return DEFAULT_GENERATOR.toJson(object); } /** * @return a JSON object representation for a map */ public static String toJson(Map m) { return DEFAULT_GENERATOR.toJson(m); } /** * Pretty print a JSON payload. * * @param jsonPayload * @return a pretty representation of JSON payload. */ public static String prettyPrint(String jsonPayload) { int indentSize = 0; // Just a guess that the pretty view will take 20 percent more than original. final CharBuf output = CharBuf.create((int) (jsonPayload.length() * 1.2)); JsonLexer lexer = new JsonLexer(new StringReader(jsonPayload)); // Will store already created indents. Map<Integer, char[]> indentCache = new HashMap<Integer, char[]>(); while (lexer.hasNext()) { JsonToken token = lexer.next(); switch (token.getType()) { case OPEN_CURLY: indentSize += 4; output.addChars(Chr.array(OPEN_BRACE, NEW_LINE)).addChars(getIndent(indentSize, indentCache)); break; case CLOSE_CURLY: indentSize -= 4; output.addChar(NEW_LINE); if (indentSize > 0) { output.addChars(getIndent(indentSize, indentCache)); } output.addChar(CLOSE_BRACE); break; case OPEN_BRACKET: indentSize += 4; output.addChars(Chr.array(OPEN_BRACKET, NEW_LINE)).addChars(getIndent(indentSize, indentCache)); break; case CLOSE_BRACKET: indentSize -= 4; output.addChar(NEW_LINE); if (indentSize > 0) { output.addChars(getIndent(indentSize, indentCache)); } output.addChar(CLOSE_BRACKET); break; case COMMA: output.addChars(Chr.array(COMMA, NEW_LINE)).addChars(getIndent(indentSize, indentCache)); break; case COLON: output.addChars(Chr.array(COLON, SPACE)); break; case STRING: String textStr = token.getText(); String textWithoutQuotes = textStr.substring(1, textStr.length() - 1); if (textWithoutQuotes.length() > 0) { output.addJsonEscapedString(textWithoutQuotes); } else { output.addQuoted(Chr.array()); } break; default: output.addString(token.getText()); } } return output.toString(); } /** * Creates new indent if it not exists in the indent cache. * * @return indent with the specified size. */ private static char[] getIndent(int indentSize, Map<Integer, char[]> indentCache) { char[] indent = indentCache.get(indentSize); if (indent == null) { indent = new char[indentSize]; Arrays.fill(indent, SPACE); indentCache.put(indentSize, indent); } return indent; } /** * Obtains JSON unescaped text for the given text * * @param text The text * @return The unescaped text */ public static JsonUnescaped unescaped(CharSequence text) { return new JsonUnescaped(text); } /** * Represents unescaped JSON */ public static class JsonUnescaped { private CharSequence text; public JsonUnescaped(CharSequence text) { this.text = text; } public CharSequence getText() { return text; } @Override public String toString() { return text.toString(); } } }