
org.glassfish.admingui.common.util.JSONUtil Maven / Gradle / Ivy
Show all versions of console-common Show documentation
/* * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at * http://www.eclipse.org/legal/epl-2.0. * * This Source Code may also be made available under the following Secondary * Licenses when the conditions for such availability set forth in the * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, * version 2 with the GNU Classpath Exception, which is available at * https://www.gnu.org/software/classpath/license.html. * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ package org.glassfish.admingui.common.util; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Stack; /** *
property * specifies how far before and after the current position that * should be returned as part of theThis class provides basic JSON encoding / decoding. It has 2 primary * methods that are of interest. The first allows you to encode a Java * Object into JSON. The other allows you to create a Java data * structure from a JSON String. See:
* **/ public class JSONUtil { private static final String ABORT_PROCESSING = "____EnD___"; private static final String COLON = "____CoLoN___"; private static final String COMMA = "____CoMmA___"; private static final String NULL = "____NuLl___"; /** *
- {@link #jsonToJava(String json)}
*- {@link #javaToJSON(Object obj, int depth)}
This method returns a Java representation of the given JSON * String. The Java data structure created will be created using * Map's, String's, Long's, Float's, Boolean's, and List's as * specified by the JSON String.
*/ public static Object jsonToJava(String json) { return replaceSpecial(jsonToJava(new JsonChars(json))); } /** *This method attempts to convert the given Object into a JSON String * to given depth. If -1 (or lower) is supplied for depth, it will * walk upto a default depth of 10 levels of the given Object. If 0 * is supplied, it will simply return "". 1 will encode the current * Object, but no children. 2 will encode the given Object and its * direct children (if any), and so on.
* *Strings, Longs, Float, and primitives are considered to not have * child Objects. Objects which have a public no-argument getXYZ() * method are considered to be child Objects. Maps and Collections * will be walked.
*/ public static String javaToJSON(Object obj, int depth) { if (depth == 0) { // Make sure we do nothing if told to do nothing... return ""; } else if (depth == -1) { // To prevent recursion... depth = 10; } String value = ""; if (obj == null) { value = "null"; } else if (obj instanceof String) { String chStr; int len; StringCharacterIterator it = new StringCharacterIterator((String) obj); char ch = it.first(); StringBuilder builder = new StringBuilder(((String) obj).length() << 2); builder.append("\""); while (ch != CharacterIterator.DONE) { switch (ch) { case '\t': builder.append("\\t"); break; case '\n': builder.append("\\n"); break; case '\r': builder.append("\\r"); break; case '\b': builder.append("\\b"); break; case '\f': builder.append("\\f"); break; case '&': case '<': case '>': case '(': case ')': case '{': case '}': case ':': case '/': case '\\': case '\'': case '"': case '=': builder.append("\\"); builder.append(ch); break; default: // Check if we should unicode escape this... if ((ch > 0x7e) || (ch < 0x20)) { builder.append("\\u"); chStr = Integer.toHexString(ch); len = chStr.length(); for (int idx=4; idx > len; idx--) { // Add leading 0's builder.append('0'); } builder.append(chStr); } else { builder.append(ch); } break; } ch = it.next(); } builder.append("\""); value = builder.toString(); } else if ((obj instanceof Boolean) || (obj instanceof Number)) { value = obj.toString(); } else if (obj instanceof Object[]) { StringBuilder builder = new StringBuilder("["); boolean first = true; for (Object element : ((Object []) obj)) { if (first) { first = false; } else { builder.append(','); } if (depth == 1) { // Treat as String, but don't try to go deeper... builder.append(javaToJSON(element.toString(), 1)); } else { // Recurse... builder.append(javaToJSON(element, depth-1)); } } builder.append("]"); value = builder.toString(); } else if (obj instanceof Map) { StringBuilder builder = new StringBuilder("{"); String key; boolean first = true; Map map = ((Map) obj); Iterator it = map.keySet().iterator(); while (it.hasNext()) { if (first) { first = false; } else { builder.append(','); } key = it.next().toString(); builder.append(javaToJSON(key, 1) + ":"); if (depth == 1) { // Treat as String, but don't try to go deeper... builder.append(javaToJSON(map.get(key).toString(), 1)); } else { // Recurse... builder.append(javaToJSON(map.get(key), depth-1)); } } builder.append("}"); value = builder.toString(); } else if (obj instanceof Collection) { StringBuilder builder = new StringBuilder("["); boolean first = true; Iterator it = ((Collection) obj).iterator(); while (it.hasNext()) { if (first) { first = false; } else { builder.append(','); } if (depth == 1) { // Treat as String, but don't try to go deeper... builder.append(javaToJSON(it.next().toString(), 1)); } else { // Recurse... builder.append(javaToJSON(it.next(), depth-1)); } } builder.append("]"); value = builder.toString(); } else { // Object StringBuilder builder = new StringBuilder("{"); String methodName; Object result; boolean first = true; Iteratorit = getGetters(obj).iterator(); while (it.hasNext()) { if (first) { first = false; } else { builder.append(','); } methodName = it.next(); // Drop "get"... builder.append(javaToJSON(methodName.substring(3), 1) + ":"); result = invokeGetter(obj, methodName); if ((result != null) && (depth == 1)) { // Treat as String, but don't try to go deeper... builder.append(javaToJSON(result.toString(), 1)); } else { // Recurse... builder.append(javaToJSON(result, depth-1)); } } builder.append("}"); value = builder.toString(); } return value; } /** * This method invokes a getter on the given object.
* *NOTE: I found a VERY similar method defined in IntegrationPoint... * at least I'm consistent. ;) These should probably be combined.
*/ private static Object invokeGetter(Object obj, String methodName) { try { return obj.getClass().getMethod(methodName).invoke(obj); } catch (Exception ex) { // Unable to execute it, return null... return null; } } /** *This method returns the names of the public no-arg getters on the * given Object.
*/ private static ListgetGetters(Object obj) { List result = new ArrayList<>(); for (Method method : obj.getClass().getMethods()) { if (method.getName().startsWith("get") && ((method.getModifiers() & Modifier.PUBLIC) != 0) && (method.getParameterTypes().length == 0) && (!method.getName().equals("getClass")) && (!method.getReturnType().getName().equals("void"))) { result.add(method.getName()); } } return result; } /** * This is the primary switching method which determines the context * in which the processing should occur.
*/ private static Object jsonToJava(JsonChars json) { Object value = null; while (json.hasNext() && (value == null)) { char ch = json.next(); switch (ch) { case '{' : value = readObject(json); break; case '[' : value = readArray(json); break; case '}' : case ']' : if (json.isAtContextEnd()) { // Stop processing value = ABORT_PROCESSING; } else { throw new IllegalArgumentException("Expected '" + json.peekContextEnd() + "' but found '" + json.current() + "' instead!"); } break; case '-' : case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : case '8' : case '9' : value = readNumber(json); break; case '\'' : case '"' : value = readString(json); break; case 'T' : case 't' : value = readConstant(json, "true"); break; case 'F' : case 'f' : value = readConstant(json, "false"); break; case 'N' : case 'n' : value = readConstant(json, "null"); break; case ' ' : case '\t' : case '\r' : case '\n' : case '\b' : case '\f' : // Ignore whitespace break; case ':' : value = COLON; break; case ',' : value = COMMA; break; default: throw new IllegalArgumentException( "Unexpected char '" + json.current() + "' near: " + json.getContext(30) + "!"); } } return value; } /** *This method creates a HashMap to represent the JSON Object.
*/ private static MapreadObject(JsonChars json) { // Save the ending char... json.pushContextEnd('}'); // Create the Map Map map = new HashMap<>(10); Object tmp = null; Object key = null; Object value = null; while (!json.isAtContextEnd()) { // Get the key key = replaceSpecial(jsonToJava(json)); if (json.isAtContextEnd()) { // Abort... break; } if (!(key instanceof String)) { throw new IllegalArgumentException( "Object keys must be a String!"); } // Get the Colon... if (!(jsonToJava(json).equals(COLON))) { throw new IllegalArgumentException( "Object keys must be followed by a colon (:)!"); } // Get the value value = replaceSpecial(jsonToJava(json)); // Get the comma between properties (may also be context end) tmp = jsonToJava(json); if ( (!(tmp.equals(COMMA))) && !json.isAtContextEnd()) { throw new IllegalArgumentException( "Expected comma (,) or end curly brace (}), but found (" + tmp + ") instead! Near: (" + json.getContext(30) + ")"); } // Add the value to the Map... map.put((String) key, value); } // Remove the context end and return json.popContextEnd(); return map; } /** * This function will process a JSON string and convert it into * an array.
*/ private static ListString
. */ String getContext(int width) { int before = loc - width; if (loc < 0) { loc = 0; } if (before < 0) { before = 0; } int after = loc + width; if (after > len) { after = len; } return string.substring(before, after - before); } /** *Returns the length of the JSON String.
*/ int getLength() { return len; } /** *Returns true if there are more characters to be parsed.
*/ boolean hasNext() { return locReturns true if the end of the current context is reached. For * example if the current context is an Object, the ending for an * Object is a '}' byte. */ boolean isAtContextEnd() { return !hasNext() || (string.charAt(loc-1) == endContext.peek()); } void pushContextEnd(char end) { endContext.push(end); } char popContextEnd() { return endContext.pop(); } char peekContextEnd() { return endContext.peek(); } } /** * This method substitutes the special Strings to their intended * representations (null, ':', and ','). This method does nothing * except return the given value if the requested value is not a * "special" value.
*/ private static Object replaceSpecial(Object val) { if (val instanceof String) { String strVal = (String) val; if (COLON.equals(strVal)) { val = ':'; } else if (COMMA.equals(strVal)) { val = ','; } else if (NULL.equals(strVal)) { val = null; } } return val; } }