* A JSONWriter instance provides a value method for appending
* values to the
* text, and a key
* method for adding keys before values in objects. There are array
* and endArray methods that make and bound array values, and
* object and endObject methods which make and bound
* object values. All of these methods return the JSONWriter instance,
* permitting a cascade style. For example,
* new JSONWriter(myWriter)
* .object()
* .key("JSON")
* .value("Hello, World!")
* .endObject(); which writes * {"JSON":"Hello, World!"}
*
* The first method called must be array or object,
* unless this object has been constructed with {@code allowSimpleValues}
* set to true.
*
* There are no methods for adding commas or colons. JSONWriter adds them for * you. Objects and arrays can be nested arbitrarily deep. *
* This can be less memory intensive than using a JSONObject to build a string.
*
* @author JSON.org
* @version 2016-09-18
*/
public class JSONWriter implements Closeable {
private static final int initdepth = 16;
/**
* Factory for creating strategy objects for writing a particular structure.
*/
private final StructureWriterFactory factory;
/**
* Strategy for writing JSON structures, parameterizes writing of
* JSON Objects and JSON Arrays, as well as pretty printing or compact.
*/
private StructureWriter currentStructure;
/**
* Indicate when writing has finished.
*/
protected boolean done;
/**
* The object/array stack.
*/
private final ALStackendArray will be appended to this array. The
* endArray method must be called to mark the array's end.
* @return this
* @throws JSONException If the nesting is too deep, or if the object is
* started in the wrong place (for example as a key or after the end of the
* outermost array or object).
*/
public JSONWriter array() throws JSONException {
if (done) {
throw new JSONWriterException("Misplaced array.", getLocation());
}
currentStructure.subValue(writer);
currentStructure = factory.newArrayWriter(currentStructure);
stack.push(currentStructure);
currentStructure.beginStructure(writer);
return this;
}
/**
* End an array. This method most be called to balance calls to
* array.
* @return this
* @throws JSONException If incorrectly nested.
*/
public JSONWriter endArray() throws JSONException {
if(done || currentStructure.getStructureType() != 'a') {
throw new JSONWriterException("Misplaced endArray.", getLocation());
}
try {
done = currentStructure.endStructure(writer);
stack.pop();
if (stack.isEmpty()) {
currentStructure = null;
done = true;
} else {
currentStructure = stack.peek();
}
} catch (JSONException e) {
throw new JSONWriterException(e, getLocation());
} catch (EmptyStackException e) {
throw new JSONWriterException(e, getLocation());
}
return this;
}
/**
* End an object. This method most be called to balance calls to
* object.
* @return this
* @throws JSONException If incorrectly nested.
*/
public JSONWriter endObject() throws JSONException {
if(done || currentStructure.getStructureType() != 'o') {
throw new JSONWriterException("Misplaced endObject.", getLocation());
}
try {
done = currentStructure.endStructure(writer);
stack.pop();
if (stack.isEmpty()) {
currentStructure = null;
done = true;
} else {
currentStructure = stack.peek();
}
} catch (JSONException e) {
throw new JSONWriterException(e, getLocation());
} catch (EmptyStackException e) {
throw new JSONWriterException(e, getLocation());
}
return this;
}
/**
* Append a key. The key will be associated with the next value. In an
* object, every value must be preceded by a key.
* @param string A key string.
* @return this
* @throws JSONException If the key is out of place. For example, keys
* do not belong in arrays or if the key is null.
*/
public JSONWriter key(String string) throws JSONException {
if(done) {
throw new JSONWriterException("Misplaced key.", getLocation());
}
try {
currentStructure.key(string, writer);
} catch (JSONException e) {
throw new JSONWriterException(e, getLocation());
}
return this;
}
/**
* Begin appending a new object. All keys and values until the balancing
* endObject will be appended to this object. The
* endObject method must be called to mark the object's end.
* @return this
* @throws JSONException If the nesting is too deep, or if the object is
* started in the wrong place (for example as a key or after the end of the
* outermost array or object).
*/
public JSONWriter object() throws JSONException {
if(done) {
throw new JSONWriterException("Misplaced object.", getLocation());
}
try {
currentStructure.subValue(writer);
currentStructure = factory.newObjectWriter(currentStructure);
stack.push(currentStructure);
currentStructure.beginStructure(writer);
} catch (JSONException e) {
throw new JSONWriterException(e, getLocation());
}
return this;
}
/**
* Append a null value.
* @return this
* @throws JSONException there was a problem writing the null value
*/
public JSONWriter nullValue() throws JSONException {
if (done) {
throw new JSONWriterException("Misplaced value.", getLocation());
}
try {
done = currentStructure.nullValue(writer);
} catch (JSONException e) {
throw new JSONWriterException(e, getLocation());
}
return this;
}
/**
* Append either the value true or the value
* false.
* @param b A boolean.
* @return this
* @throws JSONException there was a problem writing the boolean value
*/
public JSONWriter value(boolean b) throws JSONException {
if (done) {
throw new JSONWriterException("Misplaced value.", getLocation());
}
try {
done = currentStructure.booleanValue(b, writer);
} catch (JSONException e) {
throw new JSONWriterException(e.getMessage(), e, getLocation());
}
return this;
}
/**
* Append a double value.
* @param d A double.
* @return this
* @throws JSONException If the number is not finite.
*/
public JSONWriter value(double d) throws JSONException {
if (done) {
throw new JSONWriterException("Misplaced value.", getLocation());
}
try {
done = currentStructure.doubleValue(d, writer);
} catch (JSONException e) {
throw new JSONWriterException(e.getMessage(), e, getLocation());
}
return this;
}
/**
* Append a long value.
* @param l A long.
* @return this
* @throws JSONException there was a problem writing the long value
*/
public JSONWriter value(long l) throws JSONException {
if (done) {
throw new JSONWriterException("Misplaced value.", getLocation());
}
try {
done = currentStructure.longValue(l, writer);
} catch (JSONException e) {
throw new JSONWriterException(e, getLocation());
}
return this;
}
/**
* Append an object value.
* @param object The object to appendString. It can be null, or a Boolean, Number,
* String, JSONObject, or JSONArray, or an object that implements JSONString.
* @return this
* @throws JSONException If the value is out of sequence.
*/
public JSONWriter value(Object object) throws JSONException {
if (done) {
throw new JSONWriterException("Misplaced value.", getLocation());
}
try {
done = writeValue(object);
} catch (JSONWriterException e) {
throw e; // propagate
} catch (JSONException e) {
throw new JSONWriterException(e, getLocation());
}
return this;
}
/**
* Append a sequence of values into an array.
*
* @param values The objects to appendString. They can be null, or a Boolean, Number,
* String, JSONObject, or JSONArray, or an object that implements JSONString.
* @return this
* @throws JSONException If a value is out of place. For example, a value
* occurs where a key is expected.
*/
public JSONWriter values(Iterable values) throws JSONException {
if (done) {
throw new JSONWriterException("Misplaced value.", getLocation());
}
try {
for(Object obj : values) {
writeValue(obj);
}
} catch (JSONWriterException e) {
throw e; // propagate
} catch (JSONException e) {
throw new JSONWriterException(e, getLocation());
}
return this;
}
/**
* Append a sequence of keys and values in an object.
* The key will be associated with the corresponding value. In an
* object, every value must be associated with a key.
*
* @param kvPairs The objects to appendString. The values can be null, or a Boolean,
* Number, String, JSONObject, or JSONArray, or an object that implements
* JSONString.
* @return this
* @throws JSONException If a key or value is out of place. For example, keys
* do not belong in arrays or if the key is null.
*/
public JSONWriter entries(Map kvPairs) throws JSONException {
if (done) {
throw new JSONWriterException("Misplaced value.", getLocation());
}
try {
for(Entry entry : kvPairs.entrySet()) {
currentStructure.key(String.valueOf(entry.getKey()), writer);
writeValue(entry.getValue());
}
} catch (JSONWriterException e) {
throw e; // propagate
} catch (JSONException e) {
throw new JSONWriterException(e, getLocation());
}
return this;
}
/**
* Asserts the JSON writer is finished, and close any underlying
* {@code Closeable} writer.
*
* @throws IOException the writer cannot be closed
*/
@Override
public void close() throws IOException {
if(!done) {
throw new IOException("JSON stack is not empty");
}
if(writer instanceof Closeable) {
((Closeable)writer).close();
}
}
/**
* Write the contents of the Object as JSON text to a writer.
*
* Warning: This method assumes that the data structure is acyclical. * * @param value * The value to be written * @throws JSONException there was a problem writing the Object */ private boolean writeValue(Object value) throws JSONException { if(WriterUtil.isSimpleValue(value)) { return currentStructure.simpleValue(value, writer); } if (value instanceof JSONAppendable) { return jsonAppendableValue((JSONAppendable) value); } if (value instanceof JSONString) { return jsonStringValue((JSONString) value); } if (value instanceof JSONObject) { return jsonObjectValue((JSONObject) value); } if (value instanceof JSONArray) { return jsonArrayValue((JSONArray) value); } if (value instanceof Map) { return mapValue((Map) value); } if (value instanceof Iterable) { return iterableValue((Iterable) value); } if (value.getClass().isArray()) { return arrayValue(value); } if (JSONObject.objectIsBean(value)) { return beanValue(value); } return currentStructure.simpleValue(value.toString(), writer); } /** * Write the JSONAppendable as a JSON value to a writer. *
* Warning: This method assumes that the data structure is acyclical. * * @param value * The JSONString to be written * @throws JSONException there was a problem writing the JSONAppendable */ private boolean jsonAppendableValue(JSONAppendable value) throws JSONException { try { return currentStructure.jsonAppendableValue(value, writer); } catch(JSONException e) { // Propagate directly, because JSONException is a RuntimeException throw e; } catch(RuntimeException e) { throw new JSONException(e); } } /** * Write the contents of the JSONString as a JSON value to a writer. *
* Warning: This method assumes that the data structure is acyclical. * * @param value * The JSONString to be written * @throws JSONException there was a problem writing the JSONString */ private boolean jsonStringValue(JSONString value) throws JSONException { try { return currentStructure.jsonStringValue(value, writer); } catch(JSONException e) { // Propagate directly, because JSONException is a RuntimeException throw e; } catch (RuntimeException e) { throw new JSONException(e); } } /** * Write the contents of the JSONObject as JSON object to a writer. *
* Warning: This method assumes that the data structure is acyclical.
*
* @param object
* The JSONObject to be written
* @throws JSONException there was a problem writing the JSONObject
*/
private boolean jsonObjectValue(JSONObject object) throws JSONException {
Iterator
* Warning: This method assumes that the data structure is acyclical. * * @param array * The JSONArray to be written * @throws JSONException there was a problem writing the JSONArray */ private boolean jsonArrayValue(JSONArray array) throws JSONException { final int length = array.length(); array(); if (length != 0) { for (int i = 0; i < length; i += 1) { writeValue(array.get(i)); } } endArray(); return stack.isEmpty(); } /** * Write the contents of the Map as JSON object to a writer. *
* Warning: This method assumes that the data structure is acyclical. * * @param map * The Map to be written * @throws JSONException there was a problem writing the Map */ private boolean mapValue(Map map) throws JSONException { final int length = map.size(); object(); if (length != 0) { Iterator keys = map.keySet().iterator(); while (keys.hasNext()) { Object key = keys.next(); key(String.valueOf(key)).writeValue(map.get(key)); } } endObject(); return stack.isEmpty(); } /** * Write the contents of the Iterable as JSON array to a writer. *
* Warning: This method assumes that the data structure is acyclical. * * @param collection * The Iterable to be written * @throws JSONException there was a problem writing the Iterable */ private boolean iterableValue(Iterable collection) throws JSONException { Iterator iterator = collection.iterator(); array(); while (iterator.hasNext()) { writeValue(iterator.next()); } endArray(); return stack.isEmpty(); } /** * Write the contents of the array as JSON array to a writer. *
* Warning: This method assumes that the data structure is acyclical. * * @param array * The array to be written * @throws JSONException there was a problem writing the array */ private boolean arrayValue(Object array) throws JSONException { try { final int length = Array.getLength(array); array(); for (int i = 0; i < length; i += 1) { writeValue(Array.get(array, i)); } endArray(); return stack.isEmpty(); } catch (IllegalArgumentException e) { throw new JSONWriterException(e, getLocation()); } catch (ArrayIndexOutOfBoundsException e) { throw new JSONWriterException(e, getLocation()); } } /** * Write the contents of the JavaBean as JSON object to a writer. *
* Warning: This method assumes that the data structure is acyclical. * * @param bean * The bean to be written * @throws JSONException there was a problem writing the bean */ private boolean beanValue(Object bean) throws JSONException { try { // If klass is a System class then set includeSuperClass to false. Class klass = bean.getClass(); boolean includeSuperClass = klass.getClassLoader() != null; Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods(); object(); for (int i = 0; i < methods.length; i += 1) { try { Method method = methods[i]; if (Modifier.isPublic(method.getModifiers()) && !Modifier.isStatic(method.getModifiers()) && !method.isSynthetic() && (method.getReturnType() != Void.TYPE)) { String name = method.getName(); String key = JSONObject.keyFromMethodName(name); if ((key != null) && (method.getParameterTypes().length == 0)) { Object result = method.invoke(bean, (Object[]) null); if (result != null) { key(String.valueOf(key)).writeValue(result); } } } } catch (Exception ignore) { } } endObject(); return stack.isEmpty(); } catch (JSONException exception) { throw exception; } catch (RuntimeException exception) { throw new JSONException(exception); } } }