Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.cedarsoftware.util.io.JsonWriter Maven / Gradle / Ivy
package com.cedarsoftware.util.io;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Output a Java object graph in JSON format. This code handles cyclic
* references and can serialize any Object graph without requiring a class
* to be 'Serializeable' or have any specific methods on it.
*
* That's it. This can be used as a debugging tool. Output an object
* graph using the above code. You can copy that JSON output into this site
* which formats it with a lot of whitespace to make it human readable:
* http://jsonformatter.curiousconcept.com
*
*
This will output any object graph deeply (or null). Object references are
* properly handled. For example, if you had A->B, B->C, and C->A, then
* A will be serialized with a B object in it, B will be serialized with a C
* object in it, and then C will be serialized with a reference to A (ref), not a
* redefinition of A.
*
*
* @author John DeRegnaucourt ([email protected] )
*
* Copyright (c) Cedar Software LLC
*
* 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.
*/
public class JsonWriter implements Closeable, Flushable
{
public static final String CUSTOM_WRITER_MAP = "CUSTOM_WRITERS"; // If set, this map specifies Class to CustomWriter
public static final String NOT_CUSTOM_WRITER_MAP = "NOT_CUSTOM_WRITERS"; // If set, this map specifies Class to CustomWriter
public static final String DATE_FORMAT = "DATE_FORMAT"; // Set the date format to use within the JSON output
public static final String ISO_DATE_FORMAT = "yyyy-MM-dd"; // Constant for use as DATE_FORMAT value
public static final String ISO_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; // Constant for use as DATE_FORMAT value
public static final String TYPE = "TYPE"; // Force @type always
public static final String PRETTY_PRINT = "PRETTY_PRINT"; // Force nicely formatted JSON output
public static final String FIELD_SPECIFIERS = "FIELD_SPECIFIERS"; // Set value to a Map> which will be used to control which fields on a class are output
public static final String ENUM_PUBLIC_ONLY = "ENUM_PUBLIC_ONLY"; // If set, indicates that private variables of ENUMs are not to be serialized
public static final String WRITE_LONGS_AS_STRINGS = "WLAS"; // If set, longs are written in quotes (Javascript safe)
public static final String TYPE_NAME_MAP = "TYPE_NAME_MAP"; // If set, this map will be used when writing @type values - allows short-hand abbreviations type names
public static final String SHORT_META_KEYS = "SHORT_META_KEYS"; // If set, then @type -> @t, @keys -> @k, @items -> @i
public static final String SKIP_NULL_FIELDS = "SKIP_NULL"; // If set, null fields are not written
private final ConcurrentMap writers = new ConcurrentHashMap();
private final ConcurrentMap writerCache = new ConcurrentHashMap();
private final Set notCustom = new HashSet();
private static final Object[] byteStrings = new Object[256];
private static final String NEW_LINE = System.getProperty("line.separator");
private static final Long ZERO = 0L;
private static final NullClass nullWriter = new NullClass();
private final Map objVisited = new IdentityHashMap();
private final Map objsReferenced = new IdentityHashMap();
private final Writer out;
private Map typeNameMap = null;
private boolean shortMetaKeys = false;
private boolean neverShowType = false;
private boolean alwaysShowType = false;
private boolean isPrettyPrint = false;
private boolean isEnumPublicOnly = false;
private boolean writeLongsAsStrings = false;
private boolean skipNullFields = false;
long identity = 1;
private int depth = 0;
// _args is using ThreadLocal so that static inner classes can have access to them
final Map args = new HashMap();
{ // Add customer writers (these make common classes more succinct)
addWriter(String.class, new Writers.JsonStringWriter());
addWriter(Date.class, new Writers.DateWriter());
addWriter(BigInteger.class, new Writers.BigIntegerWriter());
addWriter(BigDecimal.class, new Writers.BigDecimalWriter());
addWriter(java.sql.Date.class, new Writers.DateWriter());
addWriter(Timestamp.class, new Writers.TimestampWriter());
addWriter(Calendar.class, new Writers.CalendarWriter());
addWriter(TimeZone.class, new Writers.TimeZoneWriter());
addWriter(Locale.class, new Writers.LocaleWriter());
addWriter(Class.class, new Writers.ClassWriter());
addWriter(StringBuilder.class, new Writers.StringBuilderWriter());
addWriter(StringBuffer.class, new Writers.StringBufferWriter());
}
static
{
for (short i = -128; i <= 127; i++)
{
char[] chars = Integer.toString(i).toCharArray();
byteStrings[i + 128] = chars;
}
}
public interface JsonClassWriterBase
{ }
/**
* Implement this interface to custom the JSON output for a given class.
*/
public interface JsonClassWriter extends JsonClassWriterBase
{
void write(Object o, boolean showType, Writer output) throws IOException;
boolean hasPrimitiveForm();
void writePrimitiveForm(Object o, Writer output) throws IOException;
}
/**
* Implement this interface to custom the JSON output for a given class.
*/
public interface JsonClassWriterEx extends JsonClassWriterBase
{
String JSON_WRITER = "JSON_WRITER";
void write(Object o, boolean showType, Writer output, Map args) throws IOException;
class Support
{
public static JsonWriter getWriter(Map args)
{
return (JsonWriter) args.get(JSON_WRITER);
}
}
}
/**
* Provide access to subclasses.
* @return Map containing all objects that were referenced within input object graph.
*/
public Map getObjectsReferenced()
{
return objsReferenced;
}
/**
* Provide access to subclasses.
* @return Map containing all objects that were visited within input object graph
*/
public Map getObjectsVisited()
{
return objVisited;
}
protected String getSubstituteTypeNameIfExists(String typeName)
{
if (typeNameMap == null)
{
return null;
}
return typeNameMap.get(typeName);
}
protected String getSubstituteTypeName(String typeName)
{
if (typeNameMap == null)
{
return typeName;
}
String shortName = typeNameMap.get(typeName);
return shortName == null ? typeName : shortName;
}
/**
* @see JsonWriter#objectToJson(Object, java.util.Map)
* @param item Object (root) to serialized to JSON String.
* @return String of JSON format representing complete object graph rooted by item.
*/
public static String objectToJson(Object item)
{
return objectToJson(item, null);
}
/**
* Convert a Java Object to a JSON String.
*
* @param item Object to convert to a JSON String.
* @param optionalArgs (optional) Map of extra arguments indicating how dates are formatted,
* what fields are written out (optional). For Date parameters, use the public static
* DATE_TIME key, and then use the ISO_DATE or ISO_DATE_TIME indicators. Or you can specify
* your own custom SimpleDateFormat String, or you can associate a SimpleDateFormat object,
* in which case it will be used. This setting is for both java.util.Date and java.sql.Date.
* If the DATE_FORMAT key is not used, then dates will be formatted as longs. This long can
* be turned back into a date by using 'new Date(longValue)'.
* @return String containing JSON representation of passed in object root.
*/
public static String objectToJson(Object item, Map optionalArgs)
{
try
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
JsonWriter writer = new JsonWriter(stream, optionalArgs);
writer.write(item);
writer.close();
return new String(stream.toByteArray(), "UTF-8");
}
catch (Exception e)
{
throw new JsonIoException("Unable to convert object to JSON", e);
}
}
/**
* Format the passed in JSON string in a nice, human readable format.
* @param json String input JSON
* @return String containing equivalent JSON, formatted nicely for human readability.
*/
public static String formatJson(String json)
{
Map args = new HashMap();
args.put(JsonReader.USE_MAPS, true);
Object obj = JsonReader.jsonToJava(json, args);
args.clear();
args.put(PRETTY_PRINT, true);
return objectToJson(obj, args);
}
/**
* @see JsonWriter#JsonWriter(java.io.OutputStream, java.util.Map)
* @param out OutputStream to which the JSON will be written.
*/
public JsonWriter(OutputStream out)
{
this(out, null);
}
/**
* @param out OutputStream to which the JSON output will be written.
* @param optionalArgs (optional) Map of extra arguments indicating how dates are formatted,
* what fields are written out (optional). For Date parameters, use the public static
* DATE_TIME key, and then use the ISO_DATE or ISO_DATE_TIME indicators. Or you can specify
* your own custom SimpleDateFormat String, or you can associate a SimpleDateFormat object,
* in which case it will be used. This setting is for both java.util.Date and java.sql.Date.
* If the DATE_FORMAT key is not used, then dates will be formatted as longs. This long can
* be turned back into a date by using 'new Date(longValue)'.
*/
public JsonWriter(OutputStream out, Map optionalArgs)
{
if (optionalArgs == null)
{
optionalArgs = new HashMap();
}
args.putAll(optionalArgs);
args.put(JsonClassWriterEx.JSON_WRITER, this);
typeNameMap = (Map) args.get(TYPE_NAME_MAP);
shortMetaKeys = isTrue(args.get(SHORT_META_KEYS));
alwaysShowType = isTrue(args.get(TYPE));
neverShowType = Boolean.FALSE.equals(args.get(TYPE)) || "false".equals(args.get(TYPE));
isPrettyPrint = isTrue(args.get(PRETTY_PRINT));
isEnumPublicOnly = isTrue(args.get(ENUM_PUBLIC_ONLY));
writeLongsAsStrings = isTrue(args.get(WRITE_LONGS_AS_STRINGS));
writeLongsAsStrings = isTrue(args.get(WRITE_LONGS_AS_STRINGS));
skipNullFields = isTrue(args.get(SKIP_NULL_FIELDS));
Map customWriters = (Map) args.get(CUSTOM_WRITER_MAP);
if (customWriters != null)
{
for (Map.Entry entry : customWriters.entrySet())
{
addWriter(entry.getKey(), entry.getValue());
}
}
Collection notCustomClasses = (Collection) args.get(NOT_CUSTOM_WRITER_MAP);
if (notCustomClasses != null)
{
for (Class c : notCustomClasses)
{
addNotCustomWriter(c);
}
}
if (optionalArgs.containsKey(FIELD_SPECIFIERS))
{ // Convert String field names to Java Field instances (makes it easier for user to set this up)
Map> specifiers = (Map>) args.get(FIELD_SPECIFIERS);
Map> copy = new HashMap>();
for (Entry> entry : specifiers.entrySet())
{
Class c = entry.getKey();
List fields = entry.getValue();
List newList = new ArrayList(fields.size());
Map classFields = MetaUtils.getDeepDeclaredFields(c);
for (String field : fields)
{
Field f = classFields.get(field);
if (f == null)
{
throw new JsonIoException("Unable to locate field: " + field + " on class: " + c.getName() + ". Make sure the fields in the FIELD_SPECIFIERS map existing on the associated class.");
}
newList.add(f);
}
copy.put(c, newList);
}
args.put(FIELD_SPECIFIERS, copy);
}
else
{ // Ensure that at least an empty Map is in the FIELD_SPECIFIERS entry
args.put(FIELD_SPECIFIERS, new HashMap());
}
try
{
this.out = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
}
catch (UnsupportedEncodingException e)
{
throw new JsonIoException("UTF-8 not supported on your JVM. Unable to convert object to JSON.", e);
}
}
static boolean isTrue(Object setting)
{
if (setting instanceof Boolean)
{
return Boolean.TRUE.equals(setting);
}
else if (setting instanceof String)
{
return "true".equalsIgnoreCase((String) setting);
}
else if (setting instanceof Number)
{
return ((Number)setting).intValue() != 0;
}
return false;
}
protected void tabIn() throws IOException
{
tab(out, 1);
}
protected void newLine() throws IOException
{
tab(out, 0);
}
protected void tabOut() throws IOException
{
tab(out, -1);
}
private void tab(Writer output, int delta) throws IOException
{
if (!isPrettyPrint)
{
return;
}
output.write(NEW_LINE);
depth += delta;
for (int i=0; i < depth; i++)
{
output.write(" ");
}
}
public boolean writeIfMatching(Object o, boolean showType, Writer output)
{
if (neverShowType)
{
showType = false;
}
Class c = o.getClass();
if (notCustom.contains(c))
{
return false;
}
try
{
return writeCustom(c, o, showType, output);
}
catch (IOException e)
{
throw new JsonIoException("Unable to write custom formatted object:", e);
}
}
public boolean writeArrayElementIfMatching(Class arrayComponentClass, Object o, boolean showType, Writer output)
{
if (!o.getClass().isAssignableFrom(arrayComponentClass) || notCustom.contains(o.getClass()))
{
return false;
}
try
{
return writeCustom(arrayComponentClass, o, showType, output);
}
catch (IOException e)
{
throw new JsonIoException("Unable to write custom formatted object as array element:", e);
}
}
protected boolean writeCustom(Class arrayComponentClass, Object o, boolean showType, Writer output) throws IOException
{
if (neverShowType)
{
showType = false;
}
JsonClassWriterBase closestWriter = getCustomWriter(arrayComponentClass);
if (closestWriter == null)
{
return false;
}
if (writeOptionalReference(o))
{
return true;
}
boolean referenced = objsReferenced.containsKey(o);
if (closestWriter instanceof JsonClassWriter)
{
JsonClassWriter writer = (JsonClassWriter) closestWriter;
if (writer.hasPrimitiveForm())
{
if ((!referenced && !showType) || closestWriter instanceof Writers.JsonStringWriter)
{
if (writer instanceof Writers.DateWriter)
{
((Writers.DateWriter)writer).writePrimitiveForm(o, output, args);
}
else
{
writer.writePrimitiveForm(o, output);
}
return true;
}
}
}
output.write('{');
tabIn();
if (referenced)
{
writeId(getId(o));
if (showType)
{
output.write(',');
newLine();
}
}
if (showType)
{
writeType(o, output);
}
if (referenced || showType)
{
output.write(',');
newLine();
}
if (closestWriter instanceof JsonClassWriterEx)
{
((JsonClassWriterEx)closestWriter).write(o, showType || referenced, output, args);
}
else
{
((JsonClassWriter)closestWriter).write(o, showType || referenced, output);
}
tabOut();
output.write('}');
return true;
}
/**
* Dummy place-holder class exists only because ConcurrentHashMap cannot contain a
* null value. Instead, singleton instance of this class is placed where null values
* are needed.
*/
static class NullClass implements JsonClassWriterBase { }
private JsonClassWriterBase getCustomWriter(Class c)
{
JsonClassWriterBase writer = writerCache.get(c);
if (writer == null)
{
writer = forceGetCustomWriter(c);
JsonClassWriterBase writerRef = writerCache.putIfAbsent(c, writer);
if (writerRef != null)
{
writer = writerRef;
}
}
return writer == nullWriter ? null : writer;
}
private JsonClassWriterBase forceGetCustomWriter(Class c)
{
JsonClassWriterBase closestWriter = nullWriter;
int minDistance = Integer.MAX_VALUE;
for (Map.Entry entry : writers.entrySet())
{
Class clz = entry.getKey();
if (clz == c)
{
return entry.getValue();
}
int distance = MetaUtils.getDistance(clz, c);
if (distance < minDistance)
{
minDistance = distance;
closestWriter = entry.getValue();
}
}
return closestWriter;
}
/**
* Add a custom writer which will manage writing objects of the
* passed in Class in JSON format. The custom writer will be
* called for objects of the passed in class, including subclasses.
* If this is not desired, call addNotCustomWriter(c) which will
* force objects of the passed in Class to be written by the standard
* JSON writer.
* @param c Class to associate a custom JSON writer too
* @param writer JsonClassWriterBase which implements the appropriate
* subclass of JsonClassWriterBase (JsonClassWriter or JsonClassWriterEx).
*/
public void addWriter(Class c, JsonClassWriterBase writer)
{
writers.put(c, writer);
}
/**
* For no custom writing to occur for the passed in Class.
* @param c Class which should NOT have any custom writer associated to it. Use this
* to prevent a custom writer from being used due to inheritance.
*/
public void addNotCustomWriter(Class c)
{
notCustom.add(c);
}
/**
* Write the passed in Java object in JSON format.
* @param obj Object any Java Object or JsonObject.
*/
public void write(Object obj)
{
traceReferences(obj);
objVisited.clear();
try
{
writeImpl(obj, true);
}
catch (Exception e)
{
throw new JsonIoException("Error writing object to JSON:", e);
}
flush();
objVisited.clear();
objsReferenced.clear();
writerCache.clear();
writers.clear();
}
/**
* Walk object graph and visit each instance, following each field, each Collection, Map and so on.
* Tracks visited to handle cycles and to determine if an item is referenced elsewhere. If an
* object is never referenced more than once, no @id field needs to be emitted for it.
* @param root Object to be deeply traced. The objVisited and objsReferenced Maps will be written to
* during the trace.
*/
protected void traceReferences(Object root)
{
if (root == null)
{
return;
}
Map> fieldSpecifiers = (Map) args.get(FIELD_SPECIFIERS);
final Deque stack = new ArrayDeque();
stack.addFirst(root);
final Map visited = objVisited;
final Map referenced = objsReferenced;
while (!stack.isEmpty())
{
final Object obj = stack.removeFirst();
if (!MetaUtils.isLogicalPrimitive(obj.getClass()))
{
Long id = visited.get(obj);
if (id != null)
{ // Only write an object once.
if (id == ZERO)
{ // 2nd time this object has been seen, so give it a unique ID and mark it referenced
id = identity++;
visited.put(obj, id);
referenced.put(obj, id);
}
continue;
}
else
{ // Initially, mark an object with 0 as the ID, in case it is never referenced,
// we don't waste the memory to store a Long instance that is never used.
visited.put(obj, ZERO);
}
}
final Class clazz = obj.getClass();
if (clazz.isArray())
{
if (!MetaUtils.isLogicalPrimitive(clazz.getComponentType()))
{ // Speed up: do not traceReferences of primitives, they cannot reference anything
final int len = Array.getLength(obj);
for (int i = 0; i < len; i++)
{
final Object o = Array.get(obj, i);
if (o != null)
{ // Slight perf gain (null is legal)
stack.addFirst(o);
}
}
}
}
else if (Map.class.isAssignableFrom(clazz))
{ // Speed up - logically walk maps, as opposed to following their internal structure.
Map map = (Map) obj;
for (final Object item : map.entrySet())
{
final Map.Entry entry = (Map.Entry) item;
if (entry.getValue() != null)
{
stack.addFirst(entry.getValue());
}
if (entry.getKey() != null)
{
stack.addFirst(entry.getKey());
}
}
}
else if (Collection.class.isAssignableFrom(clazz))
{
for (final Object item : (Collection)obj)
{
if (item != null)
{
stack.addFirst(item);
}
}
}
else
{ // Speed up: do not traceReferences of primitives, they cannot reference anything
if (!MetaUtils.isLogicalPrimitive(obj.getClass()))
{
traceFields(stack, obj, fieldSpecifiers);
}
}
}
}
/**
* Reach-ability trace to visit all objects within the graph to be written.
* This API will handle any object, using either reflection APIs or by
* consulting a specified FIELD_SPECIFIERS map if provided.
* @param stack Deque used to manage descent into graph (rather than using Java stack.) This allows for
* much larger graph processing.
* @param obj Object root of graph
* @param fieldSpecifiers Map of optional field specifiers, which are used to override the field list returned by
* the JDK reflection operations. This allows a subset of the actual fields on an object to be serialized.
*/
protected void traceFields(final Deque stack, final Object obj, final Map> fieldSpecifiers)
{
// If caller has special Field specifier for a given class
// then use it, otherwise use reflection.
Collection fields = getFieldsUsingSpecifier(obj.getClass(), fieldSpecifiers);
Collection fieldsBySpec = fields;
if (fields == null)
{ // Trace fields using reflection
fields = MetaUtils.getDeepDeclaredFields(obj.getClass()).values();
}
for (final Field field : fields)
{
if ((field.getModifiers() & Modifier.TRANSIENT) != 0)
{
if (fieldsBySpec == null || !fieldsBySpec.contains(field))
{ // Skip tracing transient fields EXCEPT when the field is listed explicitly by using the fieldSpecifiers Map.
// In that case, the field must be traced, even though it is transient.
continue;
}
}
try
{
final Object o = field.get(obj);
if (o != null && !MetaUtils.isLogicalPrimitive(o.getClass()))
{ // Trace through objects that can reference other objects
stack.addFirst(o);
}
}
catch (Exception ignored) { }
}
}
private static List getFieldsUsingSpecifier(final Class classBeingWritten, final Map> fieldSpecifiers)
{
final Iterator>> i = fieldSpecifiers.entrySet().iterator();
int minDistance = Integer.MAX_VALUE;
List fields = null;
while (i.hasNext())
{
final Map.Entry> entry = i.next();
final Class c = entry.getKey();
if (c == classBeingWritten)
{
return entry.getValue();
}
int distance = MetaUtils.getDistance(c, classBeingWritten);
if (distance < minDistance)
{
minDistance = distance;
fields = entry.getValue();
}
}
return fields;
}
private boolean writeOptionalReference(Object obj) throws IOException
{
if (obj == null)
{
return false;
}
if (MetaUtils.isLogicalPrimitive(obj.getClass()))
{
return false;
}
final Writer output = this.out;
if (objVisited.containsKey(obj))
{ // Only write (define) an object once in the JSON stream, otherwise emit a @ref
String id = getId(obj);
if (id == null)
{ // Test for null because of Weak/Soft references being gc'd during serialization.
return false;
}
output.write(shortMetaKeys ? "{\"@r\":" : "{\"@ref\":");
output.write(id);
output.write('}');
return true;
}
// Mark the object as visited by putting it in the Map (this map is re-used / clear()'d after walk()).
objVisited.put(obj, null);
return false;
}
/**
* Main entry point (mostly used internally, but may be called from a Custom JSON writer).
* This method will write out whatever object type it is given, including JsonObject's.
* It will handle null, detecting if a custom writer should be called, array, array of
* JsonObject, Map, Map of JsonObjects, Collection, Collection of JsonObject, any regular
* object, or a JsonObject representing a regular object.
* @param obj Object to be written
* @param showType if set to true, the @type tag will be output. If false, it will be
* dropped.
* @throws IOException if one occurs on the underlying output stream.
*/
public void writeImpl(Object obj, boolean showType) throws IOException
{
writeImpl(obj, showType, true, true);
}
/**
* Main entry point (mostly used internally, but may be called from a Custom JSON writer).
* This method will write out whatever object type it is given, including JsonObject's.
* It will handle null, detecting if a custom writer should be called, array, array of
* JsonObject, Map, Map of JsonObjects, Collection, Collection of JsonObject, any regular
* object, or a JsonObject representing a regular object.
* @param obj Object to be written
* @param showType if set to true, the @type tag will be output. If false, it will be
* @param allowRef if set to true, @ref will be used, otherwise 2+ occurrence will be
* output as full object.
* @param allowCustom if set to true, the object being called will allowed to be checked for a matching
* custom writer to be used. This does not affect subobjects, just the top-level 'obj'
* being passed in.
* @throws IOException if one occurs on the underlying output stream.
*/
public void writeImpl(Object obj, boolean showType, boolean allowRef, boolean allowCustom) throws IOException
{
if (neverShowType)
{
showType = false;
}
if (obj == null)
{
out.write("null");
return;
}
if (allowCustom && writeIfMatching(obj, showType, out))
{
return;
}
if (allowRef && writeOptionalReference(obj))
{
return;
}
if (obj.getClass().isArray())
{
writeArray(obj, showType);
}
else if (obj instanceof Collection)
{
writeCollection((Collection) obj, showType);
}
else if (obj instanceof JsonObject)
{ // symmetric support for writing Map of Maps representation back as equivalent JSON format.
JsonObject jObj = (JsonObject) obj;
if (jObj.isArray())
{
writeJsonObjectArray(jObj, showType);
}
else if (jObj.isCollection())
{
writeJsonObjectCollection(jObj, showType);
}
else if (jObj.isMap())
{
if (!writeJsonObjectMapWithStringKeys(jObj, showType))
{
writeJsonObjectMap(jObj, showType);
}
}
else
{
writeJsonObjectObject(jObj, showType);
}
}
else if (obj instanceof Map)
{
if (!writeMapWithStringKeys((Map) obj, showType))
{
writeMap((Map) obj, showType);
}
}
else
{
writeObject(obj, showType);
}
}
private void writeId(final String id) throws IOException
{
out.write(shortMetaKeys ? "\"@i\":" : "\"@id\":");
out.write(id == null ? "0" : id);
}
private void writeType(Object obj, Writer output) throws IOException
{
if (neverShowType)
{
return;
}
output.write(shortMetaKeys ? "\"@t\":\"" : "\"@type\":\"");
final Class c = obj.getClass();
String typeName = c.getName();
String shortName = getSubstituteTypeNameIfExists(typeName);
if (shortName != null)
{
output.write(shortName);
output.write('"');
return;
}
String s = c.getName();
if (s.equals("java.lang.Boolean"))
{
output.write("boolean");
}
else if (s.equals("java.lang.Byte"))
{
output.write("byte");
}
else if (s.equals("java.lang.Character"))
{
output.write("char");
}
else if (s.equals("java.lang.Class"))
{
output.write("class");
}
else if (s.equals("java.lang.Double"))
{
output.write("double");
}
else if (s.equals("java.lang.Float"))
{
output.write("float");
}
else if (s.equals("java.lang.Integer"))
{
output.write("int");
}
else if (s.equals("java.lang.Long"))
{
output.write("long");
}
else if (s.equals("java.lang.Short"))
{
output.write("short");
}
else if (s.equals("java.lang.String"))
{
output.write("string");
}
else if (s.equals("java.util.Date"))
{
output.write("date");
}
else
{
output.write(c.getName());
}
output.write('"');
}
private void writePrimitive(final Object obj, boolean showType) throws IOException
{
if (neverShowType)
{
showType = false;
}
if (obj instanceof Character)
{
writeJsonUtf8String(String.valueOf(obj), out);
}
else
{
if (obj instanceof Long && writeLongsAsStrings)
{
if (showType)
{
out.write(shortMetaKeys ? "{\"@t\":\"" : "{\"@type\":\"");
out.write(getSubstituteTypeName("long"));
out.write("\",\"value\":\"");
out.write(obj.toString());
out.write("\"}");
}
else
{
out.write('"');
out.write(obj.toString());
out.write('"');
}
}
else if (obj instanceof Double && (Double.isNaN((Double) obj) || Double.isInfinite((Double) obj)))
{
out.write("null");
}
else if (obj instanceof Float && (Float.isNaN((Float) obj) || Float.isInfinite((Float) obj)))
{
out.write("null");
}
else
{
out.write(obj.toString());
}
}
}
private void writeArray(final Object array, boolean showType) throws IOException
{
if (neverShowType)
{
showType = false;
}
Class arrayType = array.getClass();
int len = Array.getLength(array);
boolean referenced = objsReferenced.containsKey(array);
// boolean typeWritten = showType && !(Object[].class == arrayType); // causes IDE warning in NetBeans 7/4 Java 1.7
boolean typeWritten = showType && !(arrayType.equals(Object[].class));
final Writer output = this.out; // performance opt: place in final local for quicker access
if (typeWritten || referenced)
{
output.write('{');
tabIn();
}
if (referenced)
{
writeId(getId(array));
output.write(',');
newLine();
}
if (typeWritten)
{
writeType(array, output);
output.write(',');
newLine();
}
if (len == 0)
{
if (typeWritten || referenced)
{
output.write(shortMetaKeys ? "\"@e\":[]" : "\"@items\":[]");
tabOut();
output.write('}');
}
else
{
output.write("[]");
}
return;
}
if (typeWritten || referenced)
{
output.write(shortMetaKeys ? "\"@i\":[" : "\"@items\":[");
}
else
{
output.write('[');
}
tabIn();
final int lenMinus1 = len - 1;
// Intentionally processing each primitive array type in separate
// custom loop for speed. All of them could be handled using
// reflective Array.get() but it is slower. I chose speed over code length.
if (byte[].class == arrayType)
{
writeByteArray((byte[]) array, lenMinus1);
}
else if (char[].class == arrayType)
{
writeJsonUtf8String(new String((char[]) array), output);
}
else if (short[].class == arrayType)
{
writeShortArray((short[]) array, lenMinus1);
}
else if (int[].class == arrayType)
{
writeIntArray((int[]) array, lenMinus1);
}
else if (long[].class == arrayType)
{
writeLongArray((long[]) array, lenMinus1);
}
else if (float[].class == arrayType)
{
writeFloatArray((float[]) array, lenMinus1);
}
else if (double[].class == arrayType)
{
writeDoubleArray((double[]) array, lenMinus1);
}
else if (boolean[].class == arrayType)
{
writeBooleanArray((boolean[]) array, lenMinus1);
}
else
{
final Class componentClass = array.getClass().getComponentType();
final boolean isPrimitiveArray = MetaUtils.isPrimitive(componentClass);
for (int i = 0; i < len; i++)
{
final Object value = Array.get(array, i);
if (value == null)
{
output.write("null");
}
else if (writeArrayElementIfMatching(componentClass, value, false, output)) { }
else if (isPrimitiveArray || value instanceof Boolean || value instanceof Long || value instanceof Double)
{
writePrimitive(value, value.getClass() != componentClass);
}
else if (neverShowType && MetaUtils.isPrimitive(value.getClass()))
{ // When neverShowType specified, do not allow primitives to show up as {"value":6} for example.
writePrimitive(value, false);
}
else
{ // Specific Class-type arrays - only force type when
// the instance is derived from array base class.
boolean forceType = !(value.getClass() == componentClass);
writeImpl(value, forceType || alwaysShowType);
}
if (i != lenMinus1)
{
output.write(',');
newLine();
}
}
}
tabOut();
output.write(']');
if (typeWritten || referenced)
{
tabOut();
output.write('}');
}
}
private void writeBooleanArray(boolean[] booleans, int lenMinus1) throws IOException
{
final Writer output = this.out;
for (int i = 0; i < lenMinus1; i++)
{
output.write(booleans[i] ? "true," : "false,");
}
output.write(Boolean.toString(booleans[lenMinus1]));
}
private void writeDoubleArray(double[] doubles, int lenMinus1) throws IOException
{
final Writer output = this.out;
for (int i = 0; i < lenMinus1; i++)
{
output.write(Double.toString(doubles[i]));
output.write(',');
}
output.write(Double.toString(doubles[lenMinus1]));
}
private void writeFloatArray(float[] floats, int lenMinus1) throws IOException
{
final Writer output = this.out;
for (int i = 0; i < lenMinus1; i++)
{
output.write(Float.toString(floats[i]));
output.write(',');
}
output.write(Float.toString(floats[lenMinus1]));
}
private void writeLongArray(long[] longs, int lenMinus1) throws IOException
{
final Writer output = this.out;
if (writeLongsAsStrings)
{
for (int i = 0; i < lenMinus1; i++)
{
output.write('"');
output.write(Long.toString(longs[i]));
output.write('"');
output.write(',');
}
output.write('"');
output.write(Long.toString(longs[lenMinus1]));
output.write('"');
}
else
{
for (int i = 0; i < lenMinus1; i++)
{
output.write(Long.toString(longs[i]));
output.write(',');
}
output.write(Long.toString(longs[lenMinus1]));
}
}
private void writeIntArray(int[] ints, int lenMinus1) throws IOException
{
final Writer output = this.out;
for (int i = 0; i < lenMinus1; i++)
{
output.write(Integer.toString(ints[i]));
output.write(',');
}
output.write(Integer.toString(ints[lenMinus1]));
}
private void writeShortArray(short[] shorts, int lenMinus1) throws IOException
{
final Writer output = this.out;
for (int i = 0; i < lenMinus1; i++)
{
output.write(Integer.toString(shorts[i]));
output.write(',');
}
output.write(Integer.toString(shorts[lenMinus1]));
}
private void writeByteArray(byte[] bytes, int lenMinus1) throws IOException
{
final Writer output = this.out;
final Object[] byteStrs = byteStrings;
for (int i = 0; i < lenMinus1; i++)
{
output.write((char[]) byteStrs[bytes[i] + 128]);
output.write(',');
}
output.write((char[]) byteStrs[bytes[lenMinus1] + 128]);
}
private void writeCollection(Collection col, boolean showType) throws IOException
{
if (neverShowType)
{
showType = false;
}
final Writer output = this.out;
boolean referenced = objsReferenced.containsKey(col);
boolean isEmpty = col.isEmpty();
if (referenced || showType)
{
output.write('{');
tabIn();
}
else if (isEmpty)
{
output.write('[');
}
writeIdAndTypeIfNeeded(col, showType, referenced);
if (isEmpty)
{
if (referenced || showType)
{
tabOut();
output.write('}');
}
else
{
output.write(']');
}
return;
}
beginCollection(showType, referenced);
Iterator i = col.iterator();
while (i.hasNext())
{
writeCollectionElement(i.next());
if (i.hasNext())
{
output.write(',');
newLine();
}
}
tabOut();
output.write(']');
if (showType || referenced)
{ // Finished object, as it was output as an object if @id or @type was output
tabOut();
output.write("}");
}
}
private void writeIdAndTypeIfNeeded(Object col, boolean showType, boolean referenced) throws IOException
{
if (neverShowType)
{
showType = false;
}
if (referenced)
{
writeId(getId(col));
}
if (showType)
{
if (referenced)
{
out.write(',');
newLine();
}
writeType(col, out);
}
}
private void beginCollection(boolean showType, boolean referenced) throws IOException
{
if (showType || referenced)
{
out.write(',');
newLine();
out.write(shortMetaKeys ? "\"@e\":[" : "\"@items\":[");
}
else
{
out.write('[');
}
tabIn();
}
private void writeJsonObjectArray(JsonObject jObj, boolean showType) throws IOException
{
if (neverShowType)
{
showType = false;
}
int len = jObj.getLength();
String type = jObj.type;
Class arrayClass;
if (type == null || Object[].class.getName().equals(type))
{
arrayClass = Object[].class;
}
else
{
arrayClass = MetaUtils.classForName(type);
}
final Writer output = this.out;
final boolean isObjectArray = Object[].class == arrayClass;
final Class componentClass = arrayClass.getComponentType();
boolean referenced = objsReferenced.containsKey(jObj) && jObj.hasId();
boolean typeWritten = showType && !isObjectArray;
if (typeWritten || referenced)
{
output.write('{');
tabIn();
}
if (referenced)
{
writeId(Long.toString(jObj.id));
output.write(',');
newLine();
}
if (typeWritten)
{
output.write(shortMetaKeys ? "\"@t\":\"" : "\"@type\":\"");
output.write(getSubstituteTypeName(arrayClass.getName()));
output.write("\",");
newLine();
}
if (len == 0)
{
if (typeWritten || referenced)
{
output.write(shortMetaKeys ? "\"@e\":[]" : "\"@items\":[]");
tabOut();
output.write("}");
}
else
{
output.write("[]");
}
return;
}
if (typeWritten || referenced)
{
output.write(shortMetaKeys ? "\"@e\":[" : "\"@items\":[");
}
else
{
output.write('[');
}
tabIn();
Object[] items = (Object[]) jObj.get("@items");
final int lenMinus1 = len - 1;
for (int i = 0; i < len; i++)
{
final Object value = items[i];
if (value == null)
{
output.write("null");
}
else if (Character.class == componentClass || char.class == componentClass)
{
writeJsonUtf8String((String) value, output);
}
else if (value instanceof Boolean || value instanceof Long || value instanceof Double)
{
writePrimitive(value, value.getClass() != componentClass);
}
else if (neverShowType && MetaUtils.isPrimitive(value.getClass()))
{
writePrimitive(value, false);
}
else if (value instanceof String)
{ // Have to specially treat String because it could be referenced, but we still want inline (no @type, value:)
writeJsonUtf8String((String) value, output);
}
else if (writeArrayElementIfMatching(componentClass, value, false, output)) { }
else
{ // Specific Class-type arrays - only force type when
// the instance is derived from array base class.
boolean forceType = !(value.getClass() == componentClass);
writeImpl(value, forceType || alwaysShowType);
}
if (i != lenMinus1)
{
output.write(',');
newLine();
}
}
tabOut();
output.write(']');
if (typeWritten || referenced)
{
tabOut();
output.write('}');
}
}
private void writeJsonObjectCollection(JsonObject jObj, boolean showType) throws IOException
{
if (neverShowType)
{
showType = false;
}
String type = jObj.type;
Class colClass = MetaUtils.classForName(type);
boolean referenced = objsReferenced.containsKey(jObj) && jObj.hasId();
final Writer output = this.out;
int len = jObj.getLength();
if (referenced || showType || len == 0)
{
output.write('{');
tabIn();
}
if (referenced)
{
writeId(String.valueOf(jObj.id));
}
if (showType)
{
if (referenced)
{
output.write(',');
newLine();
}
output.write(shortMetaKeys ? "\"@t\":\"" : "\"@type\":\"");
output.write(getSubstituteTypeName(colClass.getName()));
output.write('"');
}
if (len == 0)
{
tabOut();
output.write('}');
return;
}
beginCollection(showType, referenced);
Object[] items = (Object[]) jObj.get("@items");
final int itemsLen = items.length;
final int itemsLenMinus1 = itemsLen - 1;
for (int i=0; i < itemsLen; i++)
{
writeCollectionElement(items[i]);
if (i != itemsLenMinus1)
{
output.write(',');
newLine();
}
}
tabOut();
output.write("]");
if (showType || referenced)
{
tabOut();
output.write('}');
}
}
private void writeJsonObjectMap(JsonObject jObj, boolean showType) throws IOException
{
if (neverShowType)
{
showType = false;
}
boolean referenced = objsReferenced.containsKey(jObj) && jObj.hasId();
final Writer output = this.out;
output.write('{');
tabIn();
if (referenced)
{
writeId(String.valueOf(jObj.getId()));
}
if (showType)
{
if (referenced)
{
output.write(',');
newLine();
}
String type = jObj.getType();
if (type != null)
{
Class mapClass = MetaUtils.classForName(type);
output.write(shortMetaKeys ? "\"@t\":\"" : "\"@type\":\"");
output.write(getSubstituteTypeName(mapClass.getName()));
output.write('"');
}
else
{ // type not displayed
showType = false;
}
}
if (jObj.isEmpty())
{ // Empty
tabOut();
output.write('}');
return;
}
if (showType)
{
output.write(',');
newLine();
}
output.write(shortMetaKeys ? "\"@k\":[" : "\"@keys\":[");
tabIn();
Iterator i = jObj.keySet().iterator();
while (i.hasNext())
{
writeCollectionElement(i.next());
if (i.hasNext())
{
output.write(',');
newLine();
}
}
tabOut();
output.write("],");
newLine();
output.write(shortMetaKeys ? "\"@e\":[" : "\"@items\":[");
tabIn();
i =jObj.values().iterator();
while (i.hasNext())
{
writeCollectionElement(i.next());
if (i.hasNext())
{
output.write(',');
newLine();
}
}
tabOut();
output.write(']');
tabOut();
output.write('}');
}
private boolean writeJsonObjectMapWithStringKeys(JsonObject jObj, boolean showType) throws IOException
{
if (neverShowType)
{
showType = false;
}
if (!ensureJsonPrimitiveKeys(jObj))
{
return false;
}
boolean referenced = objsReferenced.containsKey(jObj) && jObj.hasId();
final Writer output = this.out;
output.write('{');
tabIn();
if (referenced)
{
writeId(String.valueOf(jObj.getId()));
}
if (showType)
{
if(referenced)
{
output.write(',');
newLine();
}
String type = jObj.getType();
if (type != null)
{
Class mapClass = MetaUtils.classForName(type);
output.write(shortMetaKeys ? "\"@t\":\"" : "\"@type\":\"");
output.write(getSubstituteTypeName(mapClass.getName()));
output.write('"');
}
else
{ // type not displayed
showType = false;
}
}
if (jObj.isEmpty())
{ // Empty
tabOut();
output.write('}');
return true;
}
if (showType)
{
output.write(',');
newLine();
}
return writeMapBody(jObj.entrySet().iterator());
}
/**
* Write fields of an Object (JsonObject)
*/
private void writeJsonObjectObject(JsonObject jObj, boolean showType) throws IOException
{
if (neverShowType)
{
showType = false;
}
final Writer output = this.out;
boolean referenced = objsReferenced.containsKey(jObj) && jObj.hasId();
showType = showType && jObj.type != null;
Class type = null;
output.write('{');
tabIn();
if (referenced)
{
writeId(String.valueOf(jObj.id));
}
if (showType)
{
if (referenced)
{
output.write(',');
newLine();
}
output.write(shortMetaKeys ? "\"@t\":\"" : "\"@type\":\"");
output.write(getSubstituteTypeName(jObj.type));
output.write('"');
try { type = MetaUtils.classForName(jObj.type); } catch(Exception ignored) { type = null; }
}
if (jObj.isEmpty())
{
tabOut();
output.write('}');
return;
}
if (showType || referenced)
{
output.write(',');
newLine();
}
Iterator> i = jObj.entrySet().iterator();
boolean first = true;
while (i.hasNext())
{
Map.Entryentry = i.next();
if (skipNullFields && entry.getValue() == null)
{
continue;
}
if (!first)
{
output.write(',');
newLine();
}
first = false;
final String fieldName = entry.getKey();
output.write('"');
output.write(fieldName);
output.write("\":");
Object value = entry.getValue();
if (value == null)
{
output.write("null");
}
else if (neverShowType && MetaUtils.isPrimitive(value.getClass()))
{
writePrimitive(value, false);
}
else if (value instanceof BigDecimal || value instanceof BigInteger)
{
writeImpl(value, !doesValueTypeMatchFieldType(type, fieldName, value));
}
else if (value instanceof Number || value instanceof Boolean)
{
output.write(value.toString());
}
else if (value instanceof String)
{
writeJsonUtf8String((String) value, output);
}
else if (value instanceof Character)
{
writeJsonUtf8String(String.valueOf(value), output);
}
else
{
writeImpl(value, !doesValueTypeMatchFieldType(type, fieldName, value));
}
}
tabOut();
output.write('}');
}
private static boolean doesValueTypeMatchFieldType(Class type, String fieldName, Object value)
{
if (type != null)
{
Map classFields = MetaUtils.getDeepDeclaredFields(type);
Field field = classFields.get(fieldName);
return field != null && (value.getClass() == field.getType());
}
return false;
}
private void writeMap(Map map, boolean showType) throws IOException
{
if (neverShowType)
{
showType = false;
}
final Writer output = this.out;
boolean referenced = objsReferenced.containsKey(map);
output.write('{');
tabIn();
if (referenced)
{
writeId(getId(map));
}
if (showType)
{
if (referenced)
{
output.write(',');
newLine();
}
writeType(map, output);
}
if (map.isEmpty())
{
tabOut();
output.write('}');
return;
}
if (showType || referenced)
{
output.write(',');
newLine();
}
output.write(shortMetaKeys ? "\"@k\":[" : "\"@keys\":[");
tabIn();
Iterator i = map.keySet().iterator();
while (i.hasNext())
{
writeCollectionElement(i.next());
if (i.hasNext())
{
output.write(',');
newLine();
}
}
tabOut();
output.write("],");
newLine();
output.write(shortMetaKeys ? "\"@e\":[" : "\"@items\":[");
tabIn();
i = map.values().iterator();
while (i.hasNext())
{
writeCollectionElement(i.next());
if (i.hasNext())
{
output.write(',');
newLine();
}
}
tabOut();
output.write(']');
tabOut();
output.write('}');
}
private boolean writeMapWithStringKeys(Map map, boolean showType) throws IOException
{
if (neverShowType)
{
showType = false;
}
if (!ensureJsonPrimitiveKeys(map))
{
return false;
}
boolean referenced = objsReferenced.containsKey(map);
out.write('{');
tabIn();
writeIdAndTypeIfNeeded(map, showType, referenced);
if (map.isEmpty())
{
tabOut();
out.write('}');
return true;
}
if (showType || referenced)
{
out.write(',');
newLine();
}
return writeMapBody(map.entrySet().iterator());
}
private boolean writeMapBody(final Iterator i) throws IOException
{
final Writer output = out;
while (i.hasNext())
{
Entry att2value = (Entry) i.next();
writeJsonUtf8String((String)att2value.getKey(), output);
output.write(":");
writeCollectionElement(att2value.getValue());
if (i.hasNext())
{
output.write(',');
newLine();
}
}
tabOut();
output.write('}');
return true;
}
// Ensure that all keys within the Map are String instances
public static boolean ensureJsonPrimitiveKeys(Map map)
{
for (Object o : map.keySet())
{
if (!(o instanceof String))
{
return false;
}
}
return true;
}
/**
* Write an element that is contained in some type of Collection or Map.
* @param o Collection element to output in JSON format.
* @throws IOException if an error occurs writing to the output stream.
*/
private void writeCollectionElement(Object o) throws IOException
{
if (o == null)
{
out.write("null");
}
else if (o instanceof Boolean || o instanceof Double)
{
writePrimitive(o, false);
}
else if (o instanceof Long)
{
writePrimitive(o, writeLongsAsStrings);
}
else if (o instanceof String)
{ // Never do an @ref to a String (they are treated as logical primitives and intern'ed on read)
writeJsonUtf8String((String) o, out);
}
else if (neverShowType && MetaUtils.isPrimitive(o.getClass()))
{ // If neverShowType, then force primitives (and primitive wrappers)
// to be output with toString() - prevents {"value":6} for example
writePrimitive(o, false);
}
else
{
writeImpl(o, true);
}
}
/**
* @param obj Object to be written in JSON format
* @param showType boolean true means show the "@type" field, false
* eliminates it. Many times the type can be dropped because it can be
* inferred from the field or array type.
* @throws IOException if an error occurs writing to the output stream.
*/
private void writeObject(final Object obj, boolean showType) throws IOException
{
if (neverShowType)
{
showType = false;
}
out.write('{');
tabIn();
final boolean referenced = objsReferenced.containsKey(obj);
if (referenced)
{
writeId(getId(obj));
}
if (referenced && showType)
{
out.write(',');
newLine();
}
if (showType)
{
writeType(obj, out);
}
boolean first = !showType;
if (referenced && !showType)
{
first = false;
}
final Map> fieldSpecifiers = (Map) args.get(FIELD_SPECIFIERS);
final List externallySpecifiedFields = getFieldsUsingSpecifier(obj.getClass(), fieldSpecifiers);
if (externallySpecifiedFields != null)
{ // Caller is associating a class name to a set of fields for the given class (allows subset of fields to be written)
for (Field field : externallySpecifiedFields)
{ // Not currently supporting overwritten field names in hierarchy when using external field specifier
first = writeField(obj, first, field.getName(), field, true);
}
}
else
{ // Reflectively use fields, skipping transient and static fields
final Map classFields = MetaUtils.getDeepDeclaredFields(obj.getClass());
for (Map.Entry entry : classFields.entrySet())
{
final String fieldName = entry.getKey();
final Field field = entry.getValue();
first = writeField(obj, first, fieldName, field, false);
}
}
tabOut();
out.write('}');
}
private boolean writeField(Object obj, boolean first, String fieldName, Field field, boolean allowTransient) throws IOException
{
if (!allowTransient && (field.getModifiers() & Modifier.TRANSIENT) != 0)
{ // Do not write transient fields
return first;
}
int modifiers = field.getModifiers();
if (field.getDeclaringClass().isEnum() && !Modifier.isPublic(modifiers) && isEnumPublicOnly)
{
return first;
}
Object o;
try
{
o = field.get(obj);
}
catch (Exception ignored)
{
o = null;
}
if (skipNullFields && o == null)
{ // If skip null, skip field and return the same status on first field written indicator
return first;
}
if (!first)
{
out.write(',');
newLine();
}
writeJsonUtf8String(fieldName, out);
out.write(':');
if (o == null)
{ // don't quote null
out.write("null");
return false;
}
Class type = field.getType();
boolean forceType = o.getClass() != type; // If types are not exactly the same, write "@type" field
//When no type is written we can check the Object itself not the declaration
if (MetaUtils.isPrimitive(type) || (neverShowType && MetaUtils.isPrimitive(o.getClass())))
{
writePrimitive(o, false);
}
else
{
writeImpl(o, forceType || alwaysShowType);
}
return false;
}
/**
* Write out special characters "\b, \f, \t, \n, \r", as such, backslash as \\
* quote as \" and values less than an ASCII space (20hex) as "\\u00xx" format,
* characters in the range of ASCII space to a '~' as ASCII, and anything higher in UTF-8.
*
* @param s String to be written in UTF-8 format on the output stream.
* @param output Writer to which the UTF-8 string will be written to
* @throws IOException if an error occurs writing to the output stream.
*/
public static void writeJsonUtf8String(String s, final Writer output) throws IOException
{
output.write('\"');
final int len = s.length();
for (int i = 0; i < len; i++)
{
char c = s.charAt(i);
if (c < ' ')
{ // Anything less than ASCII space, write either in \\u00xx form, or the special \t, \n, etc. form
switch (c)
{
case '\b':
output.write("\\b");
break;
case '\f':
output.write("\\f");
break;
case '\n':
output.write("\\n");
break;
case '\r':
output.write("\\r");
break;
case '\t':
output.write("\\t");
break;
default:
String hex = Integer.toHexString(c);
output.write("\\u");
final int pad = 4 - hex.length();
for (int k = 0; k < pad; k++)
{
output.write('0');
}
output.write(hex);
break;
}
}
else if (c == '\\' || c == '"')
{
output.write('\\');
output.write(c);
}
else
{ // Anything else - write in UTF-8 form (multi-byte encoded) (OutputStreamWriter is UTF-8)
output.write(c);
}
}
output.write('\"');
}
public void flush()
{
try
{
if (out != null)
{
out.flush();
}
}
catch (Exception ignored) { }
}
public void close()
{
try
{
out.close();
}
catch (Exception ignore) { }
}
private String getId(Object o)
{
if (o instanceof JsonObject)
{
long id = ((JsonObject) o).id;
if (id != -1)
{
return String.valueOf(id);
}
}
Long id = objsReferenced.get(o);
return id == null ? null : Long.toString(id);
}
}