JSONStringer Class — spring-boot Architecture
Architecture documentation for the JSONStringer class in JSONStringer.java from the spring-boot codebase.
Entity Profile
Source Code
configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONStringer.java lines 58–429
public class JSONStringer {
/**
* The output data, containing at most one top-level array or object.
*/
final StringBuilder out = new StringBuilder();
/**
* Lexical scoping elements within this stringer, necessary to insert the appropriate
* separator characters (i.e. commas and colons) and to detect nesting errors.
*/
enum Scope {
/**
* An array with no elements requires no separators or newlines before it is
* closed.
*/
EMPTY_ARRAY,
/**
* An array with at least one value requires a comma and newline before the next
* element.
*/
NONEMPTY_ARRAY,
/**
* An object with no keys or values requires no separators or newlines before it
* is closed.
*/
EMPTY_OBJECT,
/**
* An object whose most recent element is a key. The next element must be a value.
*/
DANGLING_KEY,
/**
* An object with at least one name/value pair requires a comma and newline before
* the next element.
*/
NONEMPTY_OBJECT,
/**
* A special bracketless array needed by JSONStringer.join() and
* JSONObject.quote() only. Not used for JSON encoding.
*/
NULL
}
/**
* Unlike the original implementation, this stack isn't limited to 20 levels of
* nesting.
*/
private final List<Scope> stack = new ArrayList<>();
/**
* A string containing a full set of spaces for a single level of indentation, or null
* for no pretty printing.
*/
private final String indent;
public JSONStringer() {
this.indent = null;
}
JSONStringer(int indentSpaces) {
char[] indentChars = new char[indentSpaces];
Arrays.fill(indentChars, ' ');
this.indent = new String(indentChars);
}
/**
* Begins encoding a new array. Each call to this method must be paired with a call to
* {@link #endArray}.
* @return this stringer.
* @throws JSONException if processing of json failed
*/
public JSONStringer array() throws JSONException {
return open(Scope.EMPTY_ARRAY, "[");
}
/**
* Ends encoding the current array.
* @return this stringer.
* @throws JSONException if processing of json failed
*/
public JSONStringer endArray() throws JSONException {
return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]");
}
/**
* Begins encoding a new object. Each call to this method must be paired with a call
* to {@link #endObject}.
* @return this stringer.
* @throws JSONException if processing of json failed
*/
public JSONStringer object() throws JSONException {
return open(Scope.EMPTY_OBJECT, "{");
}
/**
* Ends encoding the current object.
* @return this stringer.
* @throws JSONException if processing of json failed
*/
public JSONStringer endObject() throws JSONException {
return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}");
}
/**
* Enters a new scope by appending any necessary whitespace and the given bracket.
* @param empty any necessary whitespace
* @param openBracket the open bracket
* @return this object
* @throws JSONException if processing of json failed
*/
JSONStringer open(Scope empty, String openBracket) throws JSONException {
if (this.stack.isEmpty() && !this.out.isEmpty()) {
throw new JSONException("Nesting problem: multiple top-level roots");
}
beforeValue();
this.stack.add(empty);
this.out.append(openBracket);
return this;
}
/**
* Closes the current scope by appending any necessary whitespace and the given
* bracket.
* @param empty any necessary whitespace
* @param nonempty the current scope
* @param closeBracket the close bracket
* @return the JSON stringer
* @throws JSONException if processing of json failed
*/
JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException {
Scope context = peek();
if (context != nonempty && context != empty) {
throw new JSONException("Nesting problem");
}
this.stack.remove(this.stack.size() - 1);
if (context == nonempty) {
newline();
}
this.out.append(closeBracket);
return this;
}
/**
* Returns the value on the top of the stack.
* @return the scope
* @throws JSONException if processing of json failed
*/
private Scope peek() throws JSONException {
if (this.stack.isEmpty()) {
throw new JSONException("Nesting problem");
}
return this.stack.get(this.stack.size() - 1);
}
/**
* Replace the value on the top of the stack with the given value.
* @param topOfStack the scope at the top of the stack
*/
private void replaceTop(Scope topOfStack) {
this.stack.set(this.stack.size() - 1, topOfStack);
}
/**
* Encodes {@code value}.
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer,
* Long, Double or null. May not be {@link Double#isNaN() NaNs} or
* {@link Double#isInfinite() infinities}.
* @return this stringer.
* @throws JSONException if processing of json failed
*/
public JSONStringer value(Object value) throws JSONException {
if (this.stack.isEmpty()) {
throw new JSONException("Nesting problem");
}
if (value instanceof JSONArray) {
((JSONArray) value).writeTo(this);
return this;
}
else if (value instanceof JSONObject) {
((JSONObject) value).writeTo(this);
return this;
}
beforeValue();
if (value == null || value instanceof Boolean || value == JSONObject.NULL) {
this.out.append(value);
}
else if (value instanceof Number) {
this.out.append(JSONObject.numberToString((Number) value));
}
else {
string(value.toString());
}
return this;
}
/**
* Encodes {@code value} to this stringer.
* @param value the value to encode
* @return this stringer.
* @throws JSONException if processing of json failed
*/
public JSONStringer value(boolean value) throws JSONException {
if (this.stack.isEmpty()) {
throw new JSONException("Nesting problem");
}
beforeValue();
this.out.append(value);
return this;
}
/**
* Encodes {@code value} to this stringer.
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
* {@link Double#isInfinite() infinities}.
* @return this stringer.
* @throws JSONException if processing of json failed
*/
public JSONStringer value(double value) throws JSONException {
if (this.stack.isEmpty()) {
throw new JSONException("Nesting problem");
}
beforeValue();
this.out.append(JSONObject.numberToString(value));
return this;
}
/**
* Encodes {@code value} to this stringer.
* @param value the value to encode
* @return this stringer.
* @throws JSONException if processing of json failed
*/
public JSONStringer value(long value) throws JSONException {
if (this.stack.isEmpty()) {
throw new JSONException("Nesting problem");
}
beforeValue();
this.out.append(value);
return this;
}
private void string(String value) {
this.out.append("\"");
for (int i = 0, length = value.length(); i < length; i++) {
char c = value.charAt(i);
/*
* From RFC 4627, "All Unicode characters may be placed within the quotation
* marks except for the characters that must be escaped: quotation mark,
* reverse solidus, and the control characters (U+0000 through U+001F)."
*/
switch (c) {
case '"', '\\', '/' -> this.out.append('\\').append(c);
case '\t' -> this.out.append("\\t");
case '\b' -> this.out.append("\\b");
case '\n' -> this.out.append("\\n");
case '\r' -> this.out.append("\\r");
case '\f' -> this.out.append("\\f");
default -> {
if (c <= 0x1F) {
this.out.append(String.format("\\u%04x", (int) c));
}
else {
this.out.append(c);
}
}
}
}
this.out.append("\"");
}
private void newline() {
if (this.indent == null) {
return;
}
this.out.append("\n");
this.out.append(this.indent.repeat(this.stack.size()));
}
/**
* Encodes the key (property name) to this stringer.
* @param name the name of the forthcoming value. May not be null.
* @return this stringer.
* @throws JSONException if processing of json failed
*/
public JSONStringer key(String name) throws JSONException {
if (name == null) {
throw new JSONException("Names must be non-null");
}
beforeKey();
string(name);
return this;
}
/**
* Inserts any necessary separators and whitespace before a name. Also adjusts the
* stack to expect the key's value.
* @throws JSONException if processing of json failed
*/
private void beforeKey() throws JSONException {
Scope context = peek();
if (context == Scope.NONEMPTY_OBJECT) { // first in object
this.out.append(',');
}
else if (context != Scope.EMPTY_OBJECT) { // not in an object!
throw new JSONException("Nesting problem");
}
newline();
replaceTop(Scope.DANGLING_KEY);
}
/**
* Inserts any necessary separators and whitespace before a literal value, inline
* array, or inline object. Also adjusts the stack to expect either a closing bracket
* or another element.
* @throws JSONException if processing of json failed
*/
private void beforeValue() throws JSONException {
if (this.stack.isEmpty()) {
return;
}
Scope context = peek();
if (context == Scope.EMPTY_ARRAY) { // first in array
replaceTop(Scope.NONEMPTY_ARRAY);
newline();
}
else if (context == Scope.NONEMPTY_ARRAY) { // another in array
this.out.append(',');
newline();
}
else if (context == Scope.DANGLING_KEY) { // value for key
this.out.append(this.indent == null ? ":" : ": ");
replaceTop(Scope.NONEMPTY_OBJECT);
}
else if (context != Scope.NULL) {
throw new JSONException("Nesting problem");
}
}
/**
* Returns the encoded JSON string.
* <p>
* If invoked with unterminated arrays or unclosed objects, this method's return value
* is undefined.
* <p>
* <strong>Warning:</strong> although it contradicts the general contract of
* {@link Object#toString}, this method returns null if the stringer contains no data.
* @return the encoded JSON string.
*/
@Override
public String toString() {
return this.out.isEmpty() ? null : this.out.toString();
}
}
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free