Java tutorial
/** * 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 ('"', 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 ('"', 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 ('"', 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("\""); } } } }