Home | History | Annotate | Download | only in json
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package org.json;
     18 
     19 import java.util.ArrayList;
     20 import java.util.Arrays;
     21 import java.util.List;
     22 
     23 // Note: this class was written without inspecting the non-free org.json sourcecode.
     24 
     25 /**
     26  * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most
     27  * application developers should use those methods directly and disregard this
     28  * API. For example:<pre>
     29  * JSONObject object = ...
     30  * String json = object.toString();</pre>
     31  *
     32  * <p>Stringers only encode well-formed JSON strings. In particular:
     33  * <ul>
     34  *   <li>The stringer must have exactly one top-level array or object.
     35  *   <li>Lexical scopes must be balanced: every call to {@link #array} must
     36  *       have a matching call to {@link #endArray} and every call to {@link
     37  *       #object} must have a matching call to {@link #endObject}.
     38  *   <li>Arrays may not contain keys (property names).
     39  *   <li>Objects must alternate keys (property names) and values.
     40  *   <li>Values are inserted with either literal {@link #value(Object) value}
     41  *       calls, or by nesting arrays or objects.
     42  * </ul>
     43  * Calls that would result in a malformed JSON string will fail with a
     44  * {@link JSONException}.
     45  *
     46  * <p>This class provides no facility for pretty-printing (ie. indenting)
     47  * output. To encode indented output, use {@link JSONObject#toString(int)} or
     48  * {@link JSONArray#toString(int)}.
     49  *
     50  * <p>Some implementations of the API support at most 20 levels of nesting.
     51  * Attempts to create more than 20 levels of nesting may fail with a {@link
     52  * JSONException}.
     53  *
     54  * <p>Each stringer may be used to encode a single top level value. Instances of
     55  * this class are not thread safe. Although this class is nonfinal, it was not
     56  * designed for inheritance and should not be subclassed. In particular,
     57  * self-use by overrideable methods is not specified. See <i>Effective Java</i>
     58  * Item 17, "Design and Document or inheritance or else prohibit it" for further
     59  * information.
     60  */
     61 public class JSONStringer {
     62 
     63     /** The output data, containing at most one top-level array or object. */
     64     final StringBuilder out = new StringBuilder();
     65 
     66     /**
     67      * Lexical scoping elements within this stringer, necessary to insert the
     68      * appropriate separator characters (ie. commas and colons) and to detect
     69      * nesting errors.
     70      */
     71     enum Scope {
     72 
     73         /**
     74          * An array with no elements requires no separators or newlines before
     75          * it is closed.
     76          */
     77         EMPTY_ARRAY,
     78 
     79         /**
     80          * A array with at least one value requires a comma and newline before
     81          * the next element.
     82          */
     83         NONEMPTY_ARRAY,
     84 
     85         /**
     86          * An object with no keys or values requires no separators or newlines
     87          * before it is closed.
     88          */
     89         EMPTY_OBJECT,
     90 
     91         /**
     92          * An object whose most recent element is a key. The next element must
     93          * be a value.
     94          */
     95         DANGLING_KEY,
     96 
     97         /**
     98          * An object with at least one name/value pair requires a comma and
     99          * newline before the next element.
    100          */
    101         NONEMPTY_OBJECT,
    102 
    103         /**
    104          * A special bracketless array needed by JSONStringer.join() and
    105          * JSONObject.quote() only. Not used for JSON encoding.
    106          */
    107         NULL,
    108     }
    109 
    110     /**
    111      * Unlike the original implementation, this stack isn't limited to 20
    112      * levels of nesting.
    113      */
    114     private final List<Scope> stack = new ArrayList<Scope>();
    115 
    116     /**
    117      * A string containing a full set of spaces for a single level of
    118      * indentation, or null for no pretty printing.
    119      */
    120     private final String indent;
    121 
    122     public JSONStringer() {
    123         indent = null;
    124     }
    125 
    126     JSONStringer(int indentSpaces) {
    127         char[] indentChars = new char[indentSpaces];
    128         Arrays.fill(indentChars, ' ');
    129         indent = new String(indentChars);
    130     }
    131 
    132     /**
    133      * Begins encoding a new array. Each call to this method must be paired with
    134      * a call to {@link #endArray}.
    135      *
    136      * @return this stringer.
    137      */
    138     public JSONStringer array() throws JSONException {
    139         return open(Scope.EMPTY_ARRAY, "[");
    140     }
    141 
    142     /**
    143      * Ends encoding the current array.
    144      *
    145      * @return this stringer.
    146      */
    147     public JSONStringer endArray() throws JSONException {
    148         return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]");
    149     }
    150 
    151     /**
    152      * Begins encoding a new object. Each call to this method must be paired
    153      * with a call to {@link #endObject}.
    154      *
    155      * @return this stringer.
    156      */
    157     public JSONStringer object() throws JSONException {
    158         return open(Scope.EMPTY_OBJECT, "{");
    159     }
    160 
    161     /**
    162      * Ends encoding the current object.
    163      *
    164      * @return this stringer.
    165      */
    166     public JSONStringer endObject() throws JSONException {
    167         return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}");
    168     }
    169 
    170     /**
    171      * Enters a new scope by appending any necessary whitespace and the given
    172      * bracket.
    173      */
    174     JSONStringer open(Scope empty, String openBracket) throws JSONException {
    175         if (stack.isEmpty() && out.length() > 0) {
    176             throw new JSONException("Nesting problem: multiple top-level roots");
    177         }
    178         beforeValue();
    179         stack.add(empty);
    180         out.append(openBracket);
    181         return this;
    182     }
    183 
    184     /**
    185      * Closes the current scope by appending any necessary whitespace and the
    186      * given bracket.
    187      */
    188     JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException {
    189         Scope context = peek();
    190         if (context != nonempty && context != empty) {
    191             throw new JSONException("Nesting problem");
    192         }
    193 
    194         stack.remove(stack.size() - 1);
    195         if (context == nonempty) {
    196             newline();
    197         }
    198         out.append(closeBracket);
    199         return this;
    200     }
    201 
    202     /**
    203      * Returns the value on the top of the stack.
    204      */
    205     private Scope peek() throws JSONException {
    206         if (stack.isEmpty()) {
    207             throw new JSONException("Nesting problem");
    208         }
    209         return stack.get(stack.size() - 1);
    210     }
    211 
    212     /**
    213      * Replace the value on the top of the stack with the given value.
    214      */
    215     private void replaceTop(Scope topOfStack) {
    216         stack.set(stack.size() - 1, topOfStack);
    217     }
    218 
    219     /**
    220      * Encodes {@code value}.
    221      *
    222      * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
    223      *     Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs}
    224      *     or {@link Double#isInfinite() infinities}.
    225      * @return this stringer.
    226      */
    227     public JSONStringer value(Object value) throws JSONException {
    228         if (stack.isEmpty()) {
    229             throw new JSONException("Nesting problem");
    230         }
    231 
    232         if (value instanceof JSONArray) {
    233             ((JSONArray) value).writeTo(this);
    234             return this;
    235 
    236         } else if (value instanceof JSONObject) {
    237             ((JSONObject) value).writeTo(this);
    238             return this;
    239         }
    240 
    241         beforeValue();
    242 
    243         if (value == null
    244                 || value instanceof Boolean
    245                 || value == JSONObject.NULL) {
    246             out.append(value);
    247 
    248         } else if (value instanceof Number) {
    249             out.append(JSONObject.numberToString((Number) value));
    250 
    251         } else {
    252             string(value.toString());
    253         }
    254 
    255         return this;
    256     }
    257 
    258     /**
    259      * Encodes {@code value} to this stringer.
    260      *
    261      * @return this stringer.
    262      */
    263     public JSONStringer value(boolean value) throws JSONException {
    264         if (stack.isEmpty()) {
    265             throw new JSONException("Nesting problem");
    266         }
    267         beforeValue();
    268         out.append(value);
    269         return this;
    270     }
    271 
    272     /**
    273      * Encodes {@code value} to this stringer.
    274      *
    275      * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
    276      *     {@link Double#isInfinite() infinities}.
    277      * @return this stringer.
    278      */
    279     public JSONStringer value(double value) throws JSONException {
    280         if (stack.isEmpty()) {
    281             throw new JSONException("Nesting problem");
    282         }
    283         beforeValue();
    284         out.append(JSONObject.numberToString(value));
    285         return this;
    286     }
    287 
    288     /**
    289      * Encodes {@code value} to this stringer.
    290      *
    291      * @return this stringer.
    292      */
    293     public JSONStringer value(long value) throws JSONException {
    294         if (stack.isEmpty()) {
    295             throw new JSONException("Nesting problem");
    296         }
    297         beforeValue();
    298         out.append(value);
    299         return this;
    300     }
    301 
    302     private void string(String value) {
    303         out.append("\"");
    304         for (int i = 0, length = value.length(); i < length; i++) {
    305             char c = value.charAt(i);
    306 
    307             /*
    308              * From RFC 4627, "All Unicode characters may be placed within the
    309              * quotation marks except for the characters that must be escaped:
    310              * quotation mark, reverse solidus, and the control characters
    311              * (U+0000 through U+001F)."
    312              */
    313             switch (c) {
    314                 case '"':
    315                 case '\\':
    316                 case '/':
    317                     out.append('\\').append(c);
    318                     break;
    319 
    320                 case '\t':
    321                     out.append("\\t");
    322                     break;
    323 
    324                 case '\b':
    325                     out.append("\\b");
    326                     break;
    327 
    328                 case '\n':
    329                     out.append("\\n");
    330                     break;
    331 
    332                 case '\r':
    333                     out.append("\\r");
    334                     break;
    335 
    336                 case '\f':
    337                     out.append("\\f");
    338                     break;
    339 
    340                 default:
    341                     if (c <= 0x1F) {
    342                         out.append(String.format("\\u%04x", (int) c));
    343                     } else {
    344                         out.append(c);
    345                     }
    346                     break;
    347             }
    348 
    349         }
    350         out.append("\"");
    351     }
    352 
    353     private void newline() {
    354         if (indent == null) {
    355             return;
    356         }
    357 
    358         out.append("\n");
    359         for (int i = 0; i < stack.size(); i++) {
    360             out.append(indent);
    361         }
    362     }
    363 
    364     /**
    365      * Encodes the key (property name) to this stringer.
    366      *
    367      * @param name the name of the forthcoming value. May not be null.
    368      * @return this stringer.
    369      */
    370     public JSONStringer key(String name) throws JSONException {
    371         if (name == null) {
    372             throw new JSONException("Names must be non-null");
    373         }
    374         beforeKey();
    375         string(name);
    376         return this;
    377     }
    378 
    379     /**
    380      * Inserts any necessary separators and whitespace before a name. Also
    381      * adjusts the stack to expect the key's value.
    382      */
    383     private void beforeKey() throws JSONException {
    384         Scope context = peek();
    385         if (context == Scope.NONEMPTY_OBJECT) { // first in object
    386             out.append(',');
    387         } else if (context != Scope.EMPTY_OBJECT) { // not in an object!
    388             throw new JSONException("Nesting problem");
    389         }
    390         newline();
    391         replaceTop(Scope.DANGLING_KEY);
    392     }
    393 
    394     /**
    395      * Inserts any necessary separators and whitespace before a literal value,
    396      * inline array, or inline object. Also adjusts the stack to expect either a
    397      * closing bracket or another element.
    398      */
    399     private void beforeValue() throws JSONException {
    400         if (stack.isEmpty()) {
    401             return;
    402         }
    403 
    404         Scope context = peek();
    405         if (context == Scope.EMPTY_ARRAY) { // first in array
    406             replaceTop(Scope.NONEMPTY_ARRAY);
    407             newline();
    408         } else if (context == Scope.NONEMPTY_ARRAY) { // another in array
    409             out.append(',');
    410             newline();
    411         } else if (context == Scope.DANGLING_KEY) { // value for key
    412             out.append(indent == null ? ":" : ": ");
    413             replaceTop(Scope.NONEMPTY_OBJECT);
    414         } else if (context != Scope.NULL) {
    415             throw new JSONException("Nesting problem");
    416         }
    417     }
    418 
    419     /**
    420      * Returns the encoded JSON string.
    421      *
    422      * <p>If invoked with unterminated arrays or unclosed objects, this method's
    423      * return value is undefined.
    424      *
    425      * <p><strong>Warning:</strong> although it contradicts the general contract
    426      * of {@link Object#toString}, this method returns null if the stringer
    427      * contains no data.
    428      */
    429     @Override public String toString() {
    430         return out.length() == 0 ? null : out.toString();
    431     }
    432 }
    433