package javaxt.json;
import javaxt.utils.Value;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Collection;
import java.lang.reflect.Array;
//******************************************************************************
//** JSONObject
//******************************************************************************
/**
* A JSON object consists key/value pairs. The string representation of a
* JSON object is a widely-used standard format for exchanging data. The
* string begins with a left brace "{" and ends with a right brace "}".
* Keys and values are separated by colon ":". Each key/value pair is
* separated by comma ",".
*
* @author Source adapted from json.org (2016-08-15)
*
******************************************************************************/
public class JSONObject extends javaxt.utils.Record {
//**************************************************************************
//** Constructor
//**************************************************************************
public JSONObject() {
super();
}
//**************************************************************************
//** Constructor
//**************************************************************************
/** Construct a JSONObject from a source JSON text string. This is the most
* commonly used JSONObject constructor.
*
* @param source A string beginning with { (left
* brace) and ending with } (right brace)
* .
*/
public JSONObject(String source) throws JSONException {
super();
if (source!=null) init(new JSONTokener(source));
}
//**************************************************************************
//** Constructor
//**************************************************************************
/** Construct a JSONObject from a JSONTokener.
*/
protected JSONObject(JSONTokener source) throws JSONException {
super();
if (source!=null) init(source);
}
private void init(JSONTokener x) throws JSONException{
char c;
String key;
if (x.nextClean() != '{') {
throw x.syntaxError("A JSONObject text must begin with '{'");
}
for (;;) {
c = x.nextClean();
switch (c) {
case 0:
throw x.syntaxError("A JSONObject text must end with '}'");
case '}':
return;
default:
x.back();
key = x.nextValue().toString();
}
// The key is followed by ':'.
c = x.nextClean();
if (c != ':') {
throw x.syntaxError("Expected a ':' after a key");
}
// Use syntaxError(..) to include error location
if (key != null) {
// Check if key exists
if (get(key).toObject() != null) {
// key already exists
//throw x.syntaxError("Duplicate key \"" + key + "\"");
}
// Only add value if non-null
Object value = x.nextValue();
if (value!=null) {
set(key, value);
}
}
// Pairs are separated by ','.
switch (x.nextClean()) {
case ';':
case ',':
if (x.nextClean() == '}') {
return;
}
x.back();
break;
case '}':
return;
default:
throw x.syntaxError("Expected a ',' or '}'");
}
}
}
//**************************************************************************
//** Constructor
//**************************************************************************
/** Construct a JSONObject from a javaxt.utils.Record.
*/
public JSONObject(javaxt.utils.Record record) throws JSONException {
for (String key : record.keySet()){
this.set(key, record.get(key));
}
}
//**************************************************************************
//** Constructor
//**************************************************************************
/** Construct a JSONObject from an XML Document.
*/
public JSONObject(org.w3c.dom.Document xml) throws JSONException {
this(javaxt.xml.DOM.getOuterNode(xml));
}
//**************************************************************************
//** Constructor
//**************************************************************************
/** Construct a JSONObject from an XML Node.
*/
public JSONObject(org.w3c.dom.Node node) throws JSONException {
JSONObject json = new JSONObject();
if (javaxt.xml.DOM.hasChildren(node)) {
traverse(node, json);
}
else{
json.set(node.getNodeName(), node.getTextContent());
}
for (String key : json.keySet()){
this.set(key, json.get(key));
}
}
private void traverse(org.w3c.dom.Node node, JSONObject json){
if (node.getNodeType()==1){
if (javaxt.xml.DOM.hasChildren(node)) {
JSONObject _json = new JSONObject();
org.w3c.dom.NodeList xmlNodeList = node.getChildNodes();
for (int i=0; i" (double quote) or
* ' (single quote).
* @return A String.
* @throws JSONException Unterminated string.
*/
private String nextString(char quote) throws JSONException {
char c;
StringBuilder sb = new StringBuilder();
for (;;) {
c = this.next();
switch (c) {
case 0:
case '\n':
case '\r':
throw this.syntaxError("Unterminated string");
case '\\':
c = this.next();
switch (c) {
case 'b':
sb.append('\b');
break;
case 't':
sb.append('\t');
break;
case 'n':
sb.append('\n');
break;
case 'f':
sb.append('\f');
break;
case 'r':
sb.append('\r');
break;
case 'u':
try {
sb.append((char)Integer.parseInt(this.next(4), 16));
} catch (NumberFormatException e) {
throw this.syntaxError("Illegal escape.", e);
}
break;
case '"':
case '\'':
case '\\':
case '/':
sb.append(c);
break;
default:
throw this.syntaxError("Illegal escape.");
}
break;
default:
if (c == quote) {
return sb.toString();
}
sb.append(c);
}
}
}
/**
* Get the text up but not including the specified character or the
* end of line, whichever comes first.
* @param delimiter A delimiter character.
* @return A string.
* @throws JSONException Thrown if there is an error while searching
* for the delimiter
*/
private String nextTo(char delimiter) throws JSONException {
StringBuilder sb = new StringBuilder();
for (;;) {
char c = this.next();
if (c == delimiter || c == 0 || c == '\n' || c == '\r') {
if (c != 0) {
this.back();
}
return sb.toString().trim();
}
sb.append(c);
}
}
/**
* Get the text up but not including one of the specified delimiter
* characters or the end of line, whichever comes first.
* @param delimiters A set of delimiter characters.
* @return A string, trimmed.
* @throws JSONException Thrown if there is an error while searching
* for the delimiter
*/
private String nextTo(String delimiters) throws JSONException {
char c;
StringBuilder sb = new StringBuilder();
for (;;) {
c = this.next();
if (delimiters.indexOf(c) >= 0 || c == 0 ||
c == '\n' || c == '\r') {
if (c != 0) {
this.back();
}
return sb.toString().trim();
}
sb.append(c);
}
}
/**
* Get the next value. The value can be a Boolean, Double, Integer,
* JSONArray, JSONObject, Long, or String.
* @throws JSONException If syntax error.
*/
protected Object nextValue() throws JSONException {
char c = this.nextClean();
String string;
switch (c) {
case '"':
case '\'':
return this.nextString(c);
case '{':
this.back();
return new JSONObject(this);
case '[':
this.back();
return new JSONArray(this);
}
/*
* Handle unquoted text. This could be the values true, false, or
* null, or it can be a number. An implementation (such as this one)
* is allowed to also accept non-standard forms.
*
* Accumulate characters until we reach the end of the text or a
* formatting character.
*/
StringBuilder sb = new StringBuilder();
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
sb.append(c);
c = this.next();
}
this.back();
string = sb.toString().trim();
if ("".equals(string)) {
throw this.syntaxError("Missing value");
}
return stringToValue(string);
}
/**
* Skip characters until the next character is the requested character.
* If the requested character is not found, no characters are skipped.
* @param to A character to skip to.
* @return The requested character, or zero if the requested character
* is not found.
* @throws JSONException Thrown if there is an error while searching
* for the to character
*/
private char skipTo(char to) throws JSONException {
char c;
try {
long startIndex = this.index;
long startCharacter = this.character;
long startLine = this.line;
this.reader.mark(1000000);
do {
c = this.next();
if (c == 0) {
// in some readers, reset() may throw an exception if
// the remaining portion of the input is greater than
// the mark size (1,000,000 above).
this.reader.reset();
this.index = startIndex;
this.character = startCharacter;
this.line = startLine;
return 0;
}
} while (c != to);
this.reader.mark(1);
} catch (IOException exception) {
throw new JSONException(exception);
}
this.back();
return c;
}
/**
* Make a JSONException to signal a syntax error.
*
* @param message The error message.
* @return A JSONException object, suitable for throwing
*/
protected JSONException syntaxError(String message) {
return new JSONException(message + this.toString());
}
/**
* Make a JSONException to signal a syntax error.
*
* @param message The error message.
* @param causedBy The throwable that caused the error.
* @return A JSONException object, suitable for throwing
*/
private JSONException syntaxError(String message, Throwable causedBy) {
return new JSONException(message + this.toString(), causedBy);
}
/**
* Make a printable string of this JSONTokener.
*
* @return " at {index} [character {character} line {line}]"
*/
@Override
public String toString() {
return " at " + this.index + " [character " + this.character + " line " +
this.line + "]";
}
//**************************************************************************
//** stringToValue
//**************************************************************************
/** Try to convert a string into a number, boolean, or null. If the string
* can't be converted, return the string.
*/
private static Object stringToValue(String string) {
if (string.equals("")) {
return string;
}
if (string.equalsIgnoreCase("true")) {
return Boolean.TRUE;
}
if (string.equalsIgnoreCase("false")) {
return Boolean.FALSE;
}
if (string.equalsIgnoreCase("null")) {
return null;
}
/*
* If it might be a number, try converting it. If a number cannot be
* produced, then the value will just be a string.
*/
char initial = string.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-') {
try {
// if we want full Big Number support this block can be replaced with:
// return stringToNumber(string);
if (isDecimalNotation(string)) {
Double d = Double.valueOf(string);
if (!d.isInfinite() && !d.isNaN()) {
return d;
}
} else {
Long myLong = Long.valueOf(string);
if (string.equals(myLong.toString())) {
if (myLong.longValue() == myLong.intValue()) {
return Integer.valueOf(myLong.intValue());
}
return myLong;
}
}
} catch (Exception ignore) {
}
}
return string;
}
//**************************************************************************
//** isDecimalNotation
//**************************************************************************
/** Tests if the value should be treated as a decimal. It makes no test if
* there are actual digits.
*
* @return true if the string is "-0" or if it contains '.', 'e', or 'E',
* false otherwise.
*/
private static boolean isDecimalNotation(final String val) {
return val.indexOf('.') > -1 || val.indexOf('e') > -1
|| val.indexOf('E') > -1 || "-0".equals(val);
}
}
}