com.outerspacecat.json.Json.java Source code

Java tutorial

Introduction

Here is the source code for com.outerspacecat.json.Json.java

Source

/**
 * Copyright 2011 Caleb Richardson
 * 
 * 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.
 */
package com.outerspacecat.json;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.io.CharSource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.CharBuffer;
import javax.annotation.Nullable;
import javax.annotation.WillNotClose;

/**
 * Defines utility methods for working with JSON data.
 * 
 * @author Caleb Richardson
 */
public final class Json {
    private Json() {
    }

    /**
     * Escapes {@code seq} according to JSON string escaping rules. The returned
     * value will not be surrounded with quotation marks ('"', U+0022).
     * Invalid UTF-16 is not reported or fixed.
     * <p>
     * Characters are escaped by replacing them with a Unicode escape sequence
     * consisting of a reverse solidus ('\', U+005C), followed by a latin small
     * letter 'u' ('u', U+0075), followed by four hexadecimal digits that
     * represent a Unicode code point.
     * <p>
     * The following characters will escaped.
     * <ol>
     * <li>Controls (U+0000-U+001F).</li>
     * <li>Quotation mark ('&quot;', U+0022).</li>
     * <li>Reverse solidus ('\', U+005C).</li>
     * <li>Surrogates (U+D800-U+DFFF).</li>
     * </ol>
     * 
     * @param seq the character sequence to escape. Must be non {@code null}.
     * @return an escaped character sequence. Never {@code null}.
     */
    public static String escapeString(final CharSequence seq) {
        Preconditions.checkNotNull(seq, "seq required");

        StringBuilder ret = new StringBuilder();

        for (int i = 0; i < seq.length(); ++i) {
            char c = seq.charAt(i);
            if ((c >= 0x00 && c <= 0x1F) || c == 0x22 || c == 0x5C || (c > 0xD800 && c <= 0xDFFF)) {
                ret.append("\\u");
                ret.append(Strings.padStart(Integer.toHexString(c), 4, '0'));
            } else {
                ret.append(c);
            }
        }

        return ret.toString();
    }

    /**
     * Unescapes {@code seq} according to JSON string escaping rules. {@code seq}
     * should not be surrounded with quotation marks ('&quot;', U+0022). Invalid
     * UTF-16 is not reported or fixed.
     * <p>
     * Both reverse solidus ('\', U+005C) escape sequences and Unicode escape
     * sequences are unescaped.
     * <p>
     * The {@code lenient} parameter controls what action to take when invalid
     * escape sequences are found. If {@code true}, then invalid Unicode escape
     * sequences will be replaced with a question mark ('?', U+003F), and invalid
     * reverse soldus escape sequences will be replaced with the character
     * following the reverse soldus. If {@code false}, then an {@link IOException}
     * will be thrown.
     * 
     * @param seq the character sequence to escape. Must be non {@code null}.
     * @param lenient whether or not the unescaping should be performed leniently
     * @return an unescaped character sequence. Never {@code null}.
     * @throws IOException if an invalid escape sequence is detected and
     *         {@code lenient} is {@code false}
     */
    public static String unescapeString(final CharSequence seq, final boolean lenient) throws IOException {
        Preconditions.checkNotNull(seq, "seq required");

        StringBuilder ret = new StringBuilder();

        boolean escaped = false;
        for (int i = 0; i < seq.length(); ++i) {
            char c = seq.charAt(i);
            if (c == '\\') {
                if (escaped)
                    ret.append('\\');
                escaped = !escaped;
            } else if (escaped) {
                if (c == '/') {
                    ret.append('/');
                } else if (c == 'b') {
                    ret.append('\b');
                } else if (c == 'f') {
                    ret.append('\f');
                } else if (c == 'n') {
                    ret.append('\n');
                } else if (c == 'r') {
                    ret.append('\r');
                } else if (c == 't') {
                    ret.append('\t');
                } else if (c == 'u') {
                    if (seq.length() - i > 4) {
                        try {
                            ret.append((char) Integer.parseInt(seq.subSequence(i + 1, i + 5).toString(), 16));
                        } catch (NumberFormatException e) {
                            if (!lenient)
                                throw new IOException(e);
                            ret.append("?");
                        }
                    } else {
                        if (!lenient)
                            throw new IOException("invalid string: " + seq);
                        ret.append("?");
                    }
                    i += 4;
                } else {
                    if (!lenient)
                        throw new IOException("invalid string: " + seq);
                    ret.append(c);
                }
                escaped = false;
            } else {
                ret.append(c);
            }
        }

        return ret.toString();
    }

    /**
     * Converts {@code obj} to a JSON token and writes it to {@code sink}.
     * {@code obj} must be {@code null} or one of the following types:
     * {@link Boolean}, {@link Byte}, {@link Short}, {@link Integer},
     * {@link Float}, {@link Double} , {@link JsonObject}, {@link JsonArray},
     * {@link CharSequence}, or {@link CharSource}.
     * <p>
     * The string types ({@link CharSequence} and {@link CharSource}) will be
     * surrounded with quotation marks ('&quot;', U+0022), JSON objects will be
     * surrounded with a left curly bracket ('{', U+007B) and a right curly
     * bracket ('}', U+007D), and JSON arrays will be surrounded with a left
     * square bracket ('[', U+005B) and a right square bracket (']', U+005D).
     * <p>
     * Instances of {@link Reader} obtained from
     * {@link CharSource#openBufferedStream()} will have {@link Reader#close()}.
     * 
     * @param obj to value to convert to JSON. May be {@code null}.
     * @param sink the character sink to write the generated JSON to. Must be non
     *        {@code null}.
     * @throws IOException if {@code obj} cannot be converted to JSON
     */
    public static void toJson(@Nullable final Object obj, @WillNotClose final Appendable sink) throws IOException {
        Preconditions.checkNotNull(sink, "sink required");

        if (obj == null || obj instanceof Boolean || obj instanceof Byte || obj instanceof Short
                || obj instanceof Integer || obj instanceof Float || obj instanceof Double) {

            sink.append(String.valueOf(obj));
        } else if (obj instanceof JsonObject) {
            ((JsonObject) obj).toJson(sink);
        } else if (obj instanceof JsonArray) {
            ((JsonArray) obj).toJson(sink);
        } else if (obj instanceof CharSequence) {
            sink.append("\"");

            CharSequence seq = (CharSequence) obj;
            for (int i = 0; i < seq.length(); i += 1024)
                sink.append(Json.escapeString(seq.subSequence(i * 1024, Math.min(i * 1024 + 1024, seq.length()))));

            sink.append("\"");
        } else if (obj instanceof CharSource) {
            try (BufferedReader src = ((CharSource) obj).openBufferedStream()) {
                sink.append("\"");

                CharBuffer cb = CharBuffer.allocate(1024);
                for (int i = src.read(cb); i != -1; i = src.read(cb)) {
                    cb.flip();
                    sink.append(Json.escapeString(cb.toString()));
                    cb.clear();
                }

                sink.append("\"");
            }
        }
    }
}