groovy.json.JsonOutput Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy-json Show documentation
Show all versions of groovy-json Show documentation
Groovy: A powerful, dynamic language for the JVM
/*
* 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 org.codehaus.groovy.runtime.DefaultGroovyMethods;
import java.io.File;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;
/**
* 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 toJson()
methods to various types.
*
* @author Guillaume Laforge
* @author Roshan Dawrani
* @author Andrey Bloschetsov
* @author Rick Hightower
* @author Graeme Rocher
*
* @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 = '"';
private static final char[] EMPTY_STRING_CHARS = Chr.array(QUOTE, QUOTE);
private static final String NULL_VALUE = "null";
private static final String JSON_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
private static final String DEFAULT_TIMEZONE = "GMT";
/**
* @return "true" or "false" for a boolean value
*/
public static String toJson(Boolean bool) {
CharBuf buffer = CharBuf.create(4);
writeObject(bool, buffer); // checking null inside
return buffer.toString();
}
/**
* @return a string representation for a number
* @throws JsonException if the number is infinite or not a number.
*/
public static String toJson(Number n) {
if (n == null) {
return NULL_VALUE;
}
CharBuf buffer = CharBuf.create(3);
Class> numberClass = n.getClass();
writeNumber(numberClass, n, buffer);
return buffer.toString();
}
/**
* @return a JSON string representation of the character
*/
public static String toJson(Character c) {
CharBuf buffer = CharBuf.create(3);
writeObject(c, buffer); // checking null inside
return buffer.toString();
}
/**
* @return a properly encoded string with escape sequences
*/
public static String toJson(String s) {
if (s == null) {
return NULL_VALUE;
}
CharBuf buffer = CharBuf.create(s.length() + 2);
writeCharSequence(s, buffer);
return buffer.toString();
}
/**
* 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) {
if (date == null) {
return NULL_VALUE;
}
CharBuf buffer = CharBuf.create(26);
writeDate(date, buffer);
return buffer.toString();
}
/**
* 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) {
if (cal == null) {
return NULL_VALUE;
}
CharBuf buffer = CharBuf.create(26);
writeDate(cal.getTime(), buffer);
return buffer.toString();
}
/**
* @return the string representation of an uuid
*/
public static String toJson(UUID uuid) {
CharBuf buffer = CharBuf.create(64);
writeObject(uuid, buffer); // checking null inside
return buffer.toString();
}
/**
* @return the string representation of the URL
*/
public static String toJson(URL url) {
CharBuf buffer = CharBuf.create(64);
writeObject(url, buffer); // checking null inside
return buffer.toString();
}
/**
* @return an object representation of a closure
*/
public static String toJson(Closure closure) {
if (closure == null) {
return NULL_VALUE;
}
CharBuf buffer = CharBuf.create(255);
writeMap(JsonDelegate.cloneDelegateAndGetContent(closure), buffer);
return buffer.toString();
}
/**
* @return an object representation of an Expando
*/
public static String toJson(Expando expando) {
if (expando == null) {
return NULL_VALUE;
}
CharBuf buffer = CharBuf.create(255);
writeMap(expando.getProperties(), buffer);
return buffer.toString();
}
/**
* @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) {
CharBuf buffer = CharBuf.create(255);
writeObject(object, buffer); // checking null inside
return buffer.toString();
}
/**
* @return a JSON object representation for a map
*/
public static String toJson(Map m) {
if (m == null) {
return NULL_VALUE;
}
CharBuf buffer = CharBuf.create(255);
writeMap(m, buffer);
return buffer.toString();
}
/**
* Serializes Number value and writes it into specified buffer.
*/
private static void writeNumber(Class> numberClass, Number value, CharBuf buffer) {
if (numberClass == Integer.class) {
buffer.addInt((Integer) value);
} else if (numberClass == Long.class) {
buffer.addLong((Long) value);
} else if (numberClass == BigInteger.class) {
buffer.addBigInteger((BigInteger) value);
} else if (numberClass == BigDecimal.class) {
buffer.addBigDecimal((BigDecimal) value);
} else if (numberClass == Double.class) {
Double doubleValue = (Double) value;
if (doubleValue.isInfinite()) {
throw new JsonException("Number " + value + " can't be serialized as JSON: infinite are not allowed in JSON.");
}
if (doubleValue.isNaN()) {
throw new JsonException("Number " + value + " can't be serialized as JSON: NaN are not allowed in JSON.");
}
buffer.addDouble(doubleValue);
} else if (numberClass == Float.class) {
Float floatValue = (Float) value;
if (floatValue.isInfinite()) {
throw new JsonException("Number " + value + " can't be serialized as JSON: infinite are not allowed in JSON.");
}
if (floatValue.isNaN()) {
throw new JsonException("Number " + value + " can't be serialized as JSON: NaN are not allowed in JSON.");
}
buffer.addFloat(floatValue);
} else if (numberClass == Byte.class) {
buffer.addByte((Byte) value);
} else if (numberClass == Short.class) {
buffer.addShort((Short) value);
} else { // Handle other Number implementations
buffer.addString(value.toString());
}
}
/**
* Serializes object and writes it into specified buffer.
*/
private static void writeObject(Object object, CharBuf buffer) {
if (object == null) {
buffer.addNull();
} else {
Class> objectClass = object.getClass();
if (CharSequence.class.isAssignableFrom(objectClass)) { // Handle String, StringBuilder, GString and other CharSequence implementations
writeCharSequence((CharSequence) object, buffer);
} else if (objectClass == Boolean.class) {
buffer.addBoolean((Boolean) object);
} else if (Number.class.isAssignableFrom(objectClass)) {
writeNumber(objectClass, (Number) object, buffer);
} else if (Date.class.isAssignableFrom(objectClass)) {
writeDate((Date) object, buffer);
} else if (Calendar.class.isAssignableFrom(objectClass)) {
writeDate(((Calendar) object).getTime(), buffer);
} else if (Map.class.isAssignableFrom(objectClass)) {
writeMap((Map) object, buffer);
} else if (Iterable.class.isAssignableFrom(objectClass)) {
writeIterator(((Iterable>) object).iterator(), buffer);
} else if (Iterator.class.isAssignableFrom(objectClass)) {
writeIterator((Iterator) object, buffer);
} else if (objectClass == Character.class) {
buffer.addJsonEscapedString(Chr.array((Character) object));
} else if (objectClass == URL.class) {
buffer.addJsonEscapedString(object.toString());
} else if (objectClass == UUID.class) {
buffer.addQuoted(object.toString());
} else if (objectClass == JsonUnescaped.class) {
buffer.add(object.toString());
} else if (Closure.class.isAssignableFrom(objectClass)) {
writeMap(JsonDelegate.cloneDelegateAndGetContent((Closure>) object), buffer);
} else if (Expando.class.isAssignableFrom(objectClass)) {
writeMap(((Expando) object).getProperties(), buffer);
} else if (Enumeration.class.isAssignableFrom(objectClass)) {
List> list = Collections.list((Enumeration>) object);
writeIterator(list.iterator(), buffer);
} else if (objectClass.isArray()) {
writeArray(objectClass, object, buffer);
} else if (Enum.class.isAssignableFrom(objectClass)) {
buffer.addQuoted(((Enum>) object).name());
}else if (File.class.isAssignableFrom(objectClass)){
Map, ?> properties = getObjectProperties(object);
//Clean up all recursive references to File objects
Iterator extends Map.Entry, ?>> iterator = properties.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry,?> entry = iterator.next();
if(entry.getValue() instanceof File){
iterator.remove();
}
}
writeMap(properties, buffer);
} else {
Map, ?> properties = getObjectProperties(object);
writeMap(properties, buffer);
}
}
}
private static Map, ?> getObjectProperties(Object object) {
Map, ?> properties = DefaultGroovyMethods.getProperties(object);
properties.remove("class");
properties.remove("declaringClass");
properties.remove("metaClass");
return properties;
}
/**
* Serializes any char sequence and writes it into specified buffer.
*/
private static void writeCharSequence(CharSequence seq, CharBuf buffer) {
if (seq.length() > 0) {
buffer.addJsonEscapedString(seq.toString());
} else {
buffer.addChars(EMPTY_STRING_CHARS);
}
}
/**
* Serializes date and writes it into specified buffer.
*/
private static void writeDate(Date date, CharBuf buffer) {
SimpleDateFormat formatter = new SimpleDateFormat(JSON_DATE_FORMAT, Locale.US);
formatter.setTimeZone(TimeZone.getTimeZone(DEFAULT_TIMEZONE));
buffer.addQuoted(formatter.format(date));
}
/**
* Serializes array and writes it into specified buffer.
*/
private static void writeArray(Class> arrayClass, Object array, CharBuf buffer) {
buffer.addChar(OPEN_BRACKET);
if (Object[].class.isAssignableFrom(arrayClass)) {
Object[] objArray = (Object[]) array;
if (objArray.length > 0) {
writeObject(objArray[0], buffer);
for (int i = 1; i < objArray.length; i++) {
buffer.addChar(COMMA);
writeObject(objArray[i], buffer);
}
}
} else if (int[].class.isAssignableFrom(arrayClass)) {
int[] intArray = (int[]) array;
if (intArray.length > 0) {
buffer.addInt(intArray[0]);
for (int i = 1; i < intArray.length; i++) {
buffer.addChar(COMMA).addInt(intArray[i]);
}
}
} else if (long[].class.isAssignableFrom(arrayClass)) {
long[] longArray = (long[]) array;
if (longArray.length > 0) {
buffer.addLong(longArray[0]);
for (int i = 1; i < longArray.length; i++) {
buffer.addChar(COMMA).addLong(longArray[i]);
}
}
} else if (boolean[].class.isAssignableFrom(arrayClass)) {
boolean[] booleanArray = (boolean[]) array;
if (booleanArray.length > 0) {
buffer.addBoolean(booleanArray[0]);
for (int i = 1; i < booleanArray.length; i++) {
buffer.addChar(COMMA).addBoolean(booleanArray[i]);
}
}
} else if (char[].class.isAssignableFrom(arrayClass)) {
char[] charArray = (char[]) array;
if (charArray.length > 0) {
buffer.addJsonEscapedString(Chr.array(charArray[0]));
for (int i = 1; i < charArray.length; i++) {
buffer.addChar(COMMA).addJsonEscapedString(Chr.array(charArray[i]));
}
}
} else if (double[].class.isAssignableFrom(arrayClass)) {
double[] doubleArray = (double[]) array;
if (doubleArray.length > 0) {
buffer.addDouble(doubleArray[0]);
for (int i = 1; i < doubleArray.length; i++) {
buffer.addChar(COMMA).addDouble(doubleArray[i]);
}
}
} else if (float[].class.isAssignableFrom(arrayClass)) {
float[] floatArray = (float[]) array;
if (floatArray.length > 0) {
buffer.addFloat(floatArray[0]);
for (int i = 1; i < floatArray.length; i++) {
buffer.addChar(COMMA).addFloat(floatArray[i]);
}
}
} else if (byte[].class.isAssignableFrom(arrayClass)) {
byte[] byteArray = (byte[]) array;
if (byteArray.length > 0) {
buffer.addByte(byteArray[0]);
for (int i = 1; i < byteArray.length; i++) {
buffer.addChar(COMMA).addByte(byteArray[i]);
}
}
} else if (short[].class.isAssignableFrom(arrayClass)) {
short[] shortArray = (short[]) array;
if (shortArray.length > 0) {
buffer.addShort(shortArray[0]);
for (int i = 1; i < shortArray.length; i++) {
buffer.addChar(COMMA).addShort(shortArray[i]);
}
}
}
buffer.addChar(CLOSE_BRACKET);
}
private static final char[] EMPTY_MAP_CHARS = {OPEN_BRACE, CLOSE_BRACE};
/**
* Serializes map and writes it into specified buffer.
*/
private static void writeMap(Map, ?> map, CharBuf buffer) {
if (!map.isEmpty()) {
buffer.addChar(OPEN_BRACE);
boolean firstItem = true;
for (Map.Entry, ?> entry : map.entrySet()) {
if (entry.getKey() == null) {
throw new IllegalArgumentException("Maps with null keys can\'t be converted to JSON");
}
if (!firstItem) {
buffer.addChar(COMMA);
} else {
firstItem = false;
}
buffer.addJsonFieldName(entry.getKey().toString());
writeObject(entry.getValue(), buffer);
}
buffer.addChar(CLOSE_BRACE);
} else {
buffer.addChars(EMPTY_MAP_CHARS);
}
}
private static final char[] EMPTY_LIST_CHARS = {OPEN_BRACKET, CLOSE_BRACKET};
/**
* Serializes iterator and writes it into specified buffer.
*/
private static void writeIterator(Iterator> iterator, CharBuf buffer) {
if (iterator.hasNext()) {
buffer.addChar(OPEN_BRACKET);
Object it = iterator.next();
writeObject(it, buffer);
while (iterator.hasNext()) {
it = iterator.next();
buffer.addChar(COMMA);
writeObject(it, buffer);
}
buffer.addChar(CLOSE_BRACKET);
} else {
buffer.addChars(EMPTY_LIST_CHARS);
}
}
/**
* 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 indentCache = new HashMap();
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 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();
}
}
}