mjson.Json Maven / Gradle / Ivy
/*
* Copyright (C) 2011 Miami-Dade County.
*
* 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.
*
* Note: this file incorporates source code from 3d party entities. Such code
* is copyrighted by those entities as indicated below.
*/
package mjson;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
//import java.util.function.Function;
import java.util.regex.Pattern;
/**
*
*
* Represents a JSON (JavaScript Object Notation) entity. For more information about JSON, please see
* http://www.json.org.
*
*
*
* A JSON entity can be one of several things: an object (set of name/Json entity pairs), an array (a list of
* other JSON entities), a string, a number, a boolean or null. All of those are represented as Json
* instances. Each of the different types of entities supports a different set of operations. However, this class
* unifies all operations into a single interface so in Java one is always dealing with a single object type: this class.
* The approach effectively amounts to dynamic typing where using an unsupported operation won't be detected at
* compile time, but will throw a runtime {@link UnsupportedOperationException}. It simplifies working with JSON
* structures considerably and it leads to shorter at cleaner Java code. It makes much easier to work
* with JSON structure without the need to convert to "proper" Java representation in the form of
* POJOs and the like. When traversing a JSON, there's no need to type-cast at each step because there's
* only one type: Json
.
*
*
*
* One can examine the concrete type of a Json
with one of the isXXX
methods:
* {@link #isObject()}, {@link #isArray()},{@link #isNumber()},{@link #isBoolean()},{@link #isString()},
* {@link #isNull()}.
*
*
*
* The underlying representation of a given Json
instance can be obtained by calling
* the generic {@link #getValue()} method or one of the asXXX
methods such
* as {@link #asBoolean()} or {@link #asString()} etc.
* JSON objects are represented as Java {@link Map}s while JSON arrays are represented as Java
* {@link List}s. Because those are mutable aggregate structures, there are two versions of the
* corresponding asXXX
methods: {@link #asMap()} which performs a deep copy of the underlying
* map, unwrapping every nested Json entity to its Java representation and {@link #asJsonMap()} which
* simply return the map reference. Similarly there are {@link #asList()} and {@link #asJsonList()}.
*
*
* Constructing and Modifying JSON Structures
*
*
* There are several static factory methods in this class that allow you to create new
* Json
instances:
*
*
*
* {@link #read(String)}
* Parse a JSON string and return the resulting Json
instance. The syntax
* recognized is as defined in http://www.json.org.
*
*
* {@link #make(Object)}
* Creates a Json instance based on the concrete type of the parameter. The types
* recognized are null, numbers, primitives, String, Map, Collection, Java arrays
* and Json
itself.
*
* {@link #nil()}
* Return a Json
instance representing JSON null
.
*
* {@link #object()}
* Create and return an empty JSON object.
*
* {@link #object(Object...)}
* Create and return a JSON object populated with the key/value pairs
* passed as an argument sequence. Each even parameter becomes a key (via
* toString
) and each odd parameter is converted to a Json
* value.
*
* {@link #array()}
* Create and return an empty JSON array.
*
* {@link #array(Object...)}
* Create and return a JSON array from the list of arguments.
*
*
*
*
* To customize how Json elements are represented and to provide your own version of the
* {@link #make(Object)} method, you create an implementation of the {@link Factory} interface
* and configure it either globally with the {@link #setGlobalFactory(Factory)} method or
* on a per-thread basis with the {@link #attachFactory(Factory)}/{@link #detachFactory()}
* methods.
*
*
*
* If a Json
instance is an object, you can set its properties by
* calling the {@link #set(String, Object)} method which will add a new property or replace an existing one.
* Adding elements to an array Json
is done with the {@link #add(Object)} method.
* Removing elements by their index (or key) is done with the {@link #delAt(int)} (or
* {@link #delAt(String)}) method. You can also remove an element from an array without
* knowing its index with the {@link #remove(Object)} method. All these methods return the
* Json
instance being manipulated so that method calls can be chained.
* If you want to remove an element from an object or array and return the removed element
* as a result of the operation, call {@link #atDel(int)} or {@link #atDel(String)} instead.
*
*
*
* If you want to add properties to an object in bulk or append a sequence of elements to array,
* use the {@link #with(Json, Json...opts)} method. When used on an object, this method expects another
* object as its argument and it will copy all properties of that argument into itself. Similarly,
* when called on array, the method expects another array and it will append all elements of its
* argument to itself.
*
*
*
* To make a clone of a Json object, use the {@link #dup()} method. This method will create a new
* object even for the immutable primitive Json types. Objects and arrays are cloned
* (i.e. duplicated) recursively.
*
*
* Navigating JSON Structures
*
*
* The {@link #at(int)} method returns the array element at the specified index and the
* {@link #at(String)} method does the same for a property of an object instance. You can
* use the {@link #at(String, Object)} version to create an object property with a default
* value if it doesn't exist already.
*
*
*
* To test just whether a Json object has a given property, use the {@link #has(String)} method. To test
* whether a given object property or an array elements is equal to a particular value, use the
* {@link #is(String, Object)} and {@link #is(int, Object)} methods respectively. Those methods return
* true if the given named property (or indexed element) is equal to the passed in Object as the second
* parameter. They return false if an object doesn't have the specified property or an index array is out
* of bounds. For example is(name, value) is equivalent to 'has(name) && at(name).equals(make(value))'.
*
*
*
* To help in navigating JSON structures, instances of this class contain a reference to the
* enclosing JSON entity (object or array) if any. The enclosing entity can be accessed
* with {@link #up()} method.
*
*
*
* The combination of method chaining when modifying Json
instances and
* the ability to navigate "inside" a structure and then go back to the enclosing
* element lets one accomplish a lot in a single Java statement, without the need
* of intermediary variables. Here for example how the following JSON structure can
* be created in one statement using chained calls:
*
*
*
* {"menu": {
* "id": "file",
* "value": "File",
* "popup": {
* "menuitem": [
* {"value": "New", "onclick": "CreateNewDoc()"},
* {"value": "Open", "onclick": "OpenDoc()"},
* {"value": "Close", "onclick": "CloseDoc()"}
* ]
* }
* "position": 0
* }}
*
*
*
* import mjson.Json;
* import static mjson.Json.*;
* ...
* Json j = object()
* .at("menu", object())
* .set("id", "file")
* .set("value", "File")
* .at("popup", object())
* .at("menuitem", array())
* .add(object("value", "New", "onclick", "CreateNewDoc()"))
* .add(object("value", "Open", "onclick", "OpenDoc()"))
* .add(object("value", "Close", "onclick", "CloseDoc()"))
* .up()
* .up()
* .set("position", 0)
* .up();
* ...
*
*
*
* If there's no danger of naming conflicts, a static import of the factory methods (
* import static json.Json.*;
) would reduce typing even further and make the code more
* readable.
*
*
* Converting to String
*
*
* To get a compact string representation, simply use the {@link #toString()} method. If you
* want to wrap it in a JavaScript callback (for JSON with padding), use the {@link #pad(String)}
* method.
*
*
* Validating with JSON Schema
*
*
* Since version 1.3, mJson supports JSON Schema, draft 4. A schema is represented by the internal
* class {@link mjson.Json.Schema}. To perform a validation, you have a instantiate a Json.Schema
* using the factory method {@link mjson.Json.Schema} and then call its validate
method
* on a JSON instance:
*
*
*
* import mjson.Json;
* import static mjson.Json.*;
* ...
* Json inputJson = Json.read(inputString);
* Json schema = Json.schema(new URI("http://mycompany.com/schemas/model"));
* Json errors = schema.validate(inputJson);
* for (Json error : errors.asJsonList())
* System.out.println("Validation error " + err);
*
* @author Borislav Iordanov
* @version 2.0.0
*/
public class Json implements java.io.Serializable, Iterable
{
private static final long serialVersionUID = 1L;
/**
*
* This interface defines how Json
instances are constructed. There is a
* default implementation for each kind of Json
value, but you can provide
* your own implementation. For example, you might want a different representation of
* an object than a regular HashMap
. Or you might want string comparison to be
* case insensitive.
*
*
*
* In addition, the {@link #make(Object)} method allows you plug-in your own mapping
* of arbitrary Java objects to Json
instances. You might want to implement
* a Java Beans to JSON mapping or any other JSON serialization that makes sense in your
* project.
*
*
*
* To avoid implementing all methods in that interface, you can extend the {@link DefaultFactory}
* default implementation and simply overwrite the ones you're interested in.
*
*
*
* The factory implementation used by the Json
classes is specified simply by calling
* the {@link #setGlobalFactory(Factory)} method. The factory is a static, global variable by default.
* If you need different factories in different areas of a single application, you may attach them
* to different threads of execution using the {@link #attachFactory(Factory)}. Recall a separate
* copy of static variables is made per ClassLoader, so for example in a web application context, that
* global factory can be different for each web application (as Java web servers usually use a separate
* class loader per application). Thread-local factories are really a provision for special cases.
*
*
* @author Borislav Iordanov
*
*/
public static interface Factory
{
/**
* Construct and return an object representing JSON null
. Implementations are
* free to cache a return the same instance. The resulting value must return
* true
from isNull()
and null
from
* getValue()
.
*
* @return The representation of a JSON null
value.
*/
Json nil();
/**
* Construct and return a JSON boolean. The resulting value must return
* true
from isBoolean()
and the passed
* in parameter from getValue()
.
* @param value The boolean value.
* @return A JSON with isBoolean() == true
. Implementations
* are free to cache and return the same instance for true and false.
*/
Json bool(boolean value);
/**
* Construct and return a JSON string. The resulting value must return
* true
from isString()
and the passed
* in parameter from getValue()
.
* @param value The string to wrap as a JSON value.
* @return A JSON element with the given string as a value.
*/
Json string(String value);
/**
* Construct and return a JSON number. The resulting value must return
* true
from isNumber()
and the passed
* in parameter from getValue()
.
*
* @param value The numeric value.
* @return Json instance representing that value.
*/
Json number(Number value);
/**
* Construct and return a JSON object. The resulting value must return
* true
from isObject()
and an implementation
* of java.util.Map
from getValue()
.
*
* @return An empty JSON object.
*/
Json object();
/**
* Construct and return a JSON object. The resulting value must return
* true
from isArray()
and an implementation
* of java.util.List
from getValue()
.
*
* @return An empty JSON array.
*/
Json array();
/**
* Construct and return a JSON object. The resulting value can be of any
* JSON type. The method is responsible for examining the type of its
* argument and performing an appropriate mapping to a Json
* instance.
*
* @param anything An arbitray Java object from which to construct a Json
* element.
* @return The newly constructed Json
instance.
*/
Json make(Object anything);
}
public static interface Function {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
/**
*
* Represents JSON schema - a specific data format that a JSON entity must
* follow. The idea of a JSON schema is very similar to XML. Its main purpose
* is validating input.
*
*
*
* More information about the various JSON schema specifications can be
* found at http://json-schema.org. JSON Schema is an IETF draft (v4 currently) and
* our implementation follows this set of specifications. A JSON schema is specified
* as a JSON object that contains keywords defined by the specification. Here are
* a few introductory materials:
*
* - http://jsonary.com/documentation/json-schema/ -
* a very well-written tutorial covering the whole standard
* - http://spacetelescope.github.io/understanding-json-schema/ -
* online book, tutorial (Python/Ruby based)
*
*
* @author Borislav Iordanov
*
*/
public static interface Schema
{
/**
*
* Validate a JSON document according to this schema. The validations attempts to
* proceed even in the face of errors. The return value is always a Json.object
* containing the boolean property ok
. When ok
is true
,
* the return object contains nothing else. When it is false
, the return object
* contains a property errors
which is an array of error messages for all
* detected schema violations.
*
*
* @param document The input document.
* @return {"ok":true}
or {"ok":false, errors:["msg1", "msg2", ...]}
*/
Json validate(Json document);
/**
* Return the JSON representation of the schema.
*/
Json toJson();
/**
* Possible options are: ignoreDefaults:true|false
.
*
* @return A newly created Json
conforming to this schema.
*/
//Json generate(Json options);
}
@Override
public Iterator iterator()
{
return new Iterator()
{
@Override
public boolean hasNext() { return false; }
@Override
public Json next() { return null; }
@Override
public void remove() { }
};
}
static String fetchContent(URL url)
{
java.io.Reader reader = null;
try
{
reader = new java.io.InputStreamReader((java.io.InputStream)url.getContent());
StringBuilder content = new StringBuilder();
char [] buf = new char[1024];
for (int n = reader.read(buf); n > -1; n = reader.read(buf))
content.append(buf, 0, n);
return content.toString();
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
finally
{
if (reader != null) try { reader.close(); } catch (Throwable t) { }
}
}
static Json resolvePointer(String pointerRepresentation, Json top)
{
String [] parts = pointerRepresentation.split("/");
Json result = top;
for (String p : parts)
{
// TODO: unescaping and decoding
if (p.length() == 0)
continue;
p = p.replace("~1", "/").replace("~0", "~");
if (result.isArray())
result = result.at(Integer.parseInt(p));
else if (result.isObject())
result = result.at(p);
else
throw new RuntimeException("Can't resolve pointer " + pointerRepresentation +
" on document " + top.toString(200));
}
return result;
}
static URI makeAbsolute(URI base, String ref) throws Exception
{
URI refuri;
if (base != null && base.getAuthority() != null && !new URI(ref).isAbsolute())
{
StringBuilder sb = new StringBuilder();
if (base.getScheme() != null)
sb.append(base.getScheme()).append("://");
sb.append(base.getAuthority());
if (!ref.startsWith("/"))
{
if (ref.startsWith("#"))
sb.append(base.getPath());
else
{
int slashIdx = base.getPath().lastIndexOf('/');
sb.append(slashIdx == -1 ? base.getPath() : base.getPath().substring(0, slashIdx)).append("/");
}
}
refuri = new URI(sb.append(ref).toString());
}
else if (base != null)
refuri = base.resolve(ref);
else
refuri = new URI(ref);
return refuri;
}
static Json resolveRef(URI base,
Json refdoc,
URI refuri,
Map resolved,
Map expanded,
Function uriResolver) throws Exception
{
if (refuri.isAbsolute() &&
(base == null || !base.isAbsolute() ||
!base.getScheme().equals(refuri.getScheme()) ||
!Objects.equals(base.getHost(), refuri.getHost()) ||
base.getPort() != refuri.getPort() ||
!base.getPath().equals(refuri.getPath())))
{
URI docuri = null;
refuri = refuri.normalize();
if (refuri.getHost() == null)
docuri = new URI(refuri.getScheme() + ":" + refuri.getPath());
else
docuri = new URI(refuri.getScheme() + "://" + refuri.getHost() +
((refuri.getPort() > -1) ? ":" + refuri.getPort() : "") +
refuri.getPath());
refdoc = uriResolver.apply(docuri);
refdoc = expandReferences(refdoc, refdoc, docuri, resolved, expanded, uriResolver);
}
if (refuri.getFragment() == null)
return refdoc;
else
return resolvePointer(refuri.getFragment(), refdoc);
}
/**
*
* Replace all JSON references, as per the http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03
* specification, by their referants.
*
* @param json
* @param duplicate
* @param done
* @return
*/
static Json expandReferences(Json json,
Json topdoc,
URI base,
Map resolved,
Map expanded,
Function uriResolver) throws Exception
{
if (expanded.containsKey(json)) return json;
if (json.isObject())
{
if (json.has("id") && json.at("id").isString()) // change scope of nest references
{
base = base.resolve(json.at("id").asString());
}
if (json.has("$ref"))
{
URI refuri = makeAbsolute(base, json.at("$ref").asString()); // base.resolve(json.at("$ref").asString());
Json ref = resolved.get(refuri.toString());
if (ref == null)
{
ref = Json.object();
resolved.put(refuri.toString(), ref);
ref.with(resolveRef(base, topdoc, refuri, resolved, expanded, uriResolver));
}
json = ref;
}
else
{
for (Map.Entry e : json.asJsonMap().entrySet())
json.set(e.getKey(), expandReferences(e.getValue(), topdoc, base, resolved, expanded, uriResolver));
}
}
else if (json.isArray())
{
for (int i = 0; i < json.asJsonList().size(); i++)
json.set(i,
expandReferences(json.at(i), topdoc, base, resolved, expanded, uriResolver));
}
expanded.put(json, json);
return json;
}
static class DefaultSchema implements Schema
{
static interface Instruction extends Function{}
static Json maybeError(Json errors, Json E)
{ return E == null ? errors : (errors == null ? Json.array() : errors).with(E, new Json[0]); }
// Anything is valid schema
static Instruction any = new Instruction() { public Json apply(Json param) { return null; } };
// Type validation
class IsObject implements Instruction { public Json apply(Json param)
{ return param.isObject() ? null : Json.make(param.toString(maxchars)); } }
class IsArray implements Instruction { public Json apply(Json param)
{ return param.isArray() ? null : Json.make(param.toString(maxchars)); } }
class IsString implements Instruction { public Json apply(Json param)
{ return param.isString() ? null : Json.make(param.toString(maxchars)); } }
class IsBoolean implements Instruction { public Json apply(Json param)
{ return param.isBoolean() ? null : Json.make(param.toString(maxchars)); } }
class IsNull implements Instruction { public Json apply(Json param)
{ return param.isNull() ? null : Json.make(param.toString(maxchars)); } }
class IsNumber implements Instruction { public Json apply(Json param)
{ return param.isNumber() ? null : Json.make(param.toString(maxchars)); } }
class IsInteger implements Instruction { public Json apply(Json param)
{ return param.isNumber() && ((Number)param.getValue()) instanceof Integer ? null : Json.make(param.toString(maxchars)); } }
class CheckString implements Instruction
{
int min = 0, max = Integer.MAX_VALUE;
Pattern pattern;
public Json apply(Json param)
{
Json errors = null;
if (!param.isString()) return errors;
String s = param.asString();
final int size = s.codePointCount(0, s.length());
if (size < min || size > max)
errors = maybeError(errors,Json.make("String " + param.toString(maxchars) +
" has length outside of the permitted range [" + min + "," + max + "]."));
if (pattern != null && !pattern.matcher(s).matches())
errors = maybeError(errors,Json.make("String " + param.toString(maxchars) +
" does not match regex " + pattern.toString()));
return errors;
}
}
class CheckNumber implements Instruction
{
double min = Double.NaN, max = Double.NaN, multipleOf = Double.NaN;
boolean exclusiveMin = false, exclusiveMax = false;
public Json apply(Json param)
{
Json errors = null;
if (!param.isNumber()) return errors;
double value = param.asDouble();
if (!Double.isNaN(min) && (value < min || exclusiveMin && value == min))
errors = maybeError(errors,Json.make("Number " + param + " is below allowed minimum " + min));
if (!Double.isNaN(max) && (value > max || exclusiveMax && value == max))
errors = maybeError(errors,Json.make("Number " + param + " is above allowed maximum " + max));
if (!Double.isNaN(multipleOf) && (value / multipleOf) % 1 != 0)
errors = maybeError(errors,Json.make("Number " + param + " is not a multiple of " + multipleOf));
return errors;
}
}
class CheckArray implements Instruction
{
int min = 0, max = Integer.MAX_VALUE;
Boolean uniqueitems = null;
Instruction additionalSchema = any;
Instruction schema;
ArrayList schemas;
public Json apply(Json param)
{
Json errors = null;
if (!param.isArray()) return errors;
if (schema == null && schemas == null && additionalSchema == null) // no schema specified
return errors;
int size = param.asJsonList().size();
for (int i = 0; i < size; i++)
{
Instruction S = schema != null ? schema
: (schemas != null && i < schemas.size()) ? schemas.get(i) : additionalSchema;
if (S == null)
errors = maybeError(errors,Json.make("Additional items are not permitted: " +
param.at(i) + " in " + param.toString(maxchars)));
else
errors = maybeError(errors, S.apply(param.at(i)));
if (uniqueitems != null && uniqueitems && param.asJsonList().lastIndexOf(param.at(i)) > i)
errors = maybeError(errors,Json.make("Element " + param.at(i) + " is duplicate in array."));
if (errors != null && !errors.asJsonList().isEmpty())
break;
}
if (size < min || size > max)
errors = maybeError(errors,Json.make("Array " + param.toString(maxchars) +
" has number of elements outside of the permitted range [" + min + "," + max + "]."));
return errors;
}
}
class CheckPropertyPresent implements Instruction
{
String propname;
public CheckPropertyPresent(String propname) { this.propname = propname; }
public Json apply(Json param)
{
if (!param.isObject()) return null;
if (param.has(propname)) return null;
else return Json.array().add(Json.make("Required property " + propname +
" missing from object " + param.toString(maxchars)));
}
}
class CheckObject implements Instruction
{
int min = 0, max = Integer.MAX_VALUE;
Instruction additionalSchema = any;
ArrayList props = new ArrayList();
ArrayList patternProps = new ArrayList();
// Object validation
class CheckProperty implements Instruction
{
String name;
Instruction schema;
public CheckProperty(String name, Instruction schema)
{ this.name = name; this.schema = schema; }
public Json apply(Json param)
{
Json value = param.at(name);
if (value == null)
return null;
else
return schema.apply(param.at(name));
}
}
class CheckPatternProperty // implements Instruction
{
Pattern pattern;
Instruction schema;
public CheckPatternProperty(String pattern, Instruction schema)
{ this.pattern = Pattern.compile(pattern); this.schema = schema; }
public Json apply(Json param, Set found)
{
Json errors = null;
for (Map.Entry e : param.asJsonMap().entrySet())
if (pattern.matcher(e.getKey()).find()) {
found.add(e.getKey());
errors = maybeError(errors, schema.apply(e.getValue()));
}
return errors;
}
}
public Json apply(Json param)
{
Json errors = null;
if (!param.isObject()) return errors;
HashSet checked = new HashSet();
for (CheckProperty I : props) {
if (param.has(I.name)) checked.add(I.name);
errors = maybeError(errors, I.apply(param));
}
for (CheckPatternProperty I : patternProps) {
errors = maybeError(errors, I.apply(param, checked));
}
if (additionalSchema != any) for (Map.Entry e : param.asJsonMap().entrySet())
if (!checked.contains(e.getKey()))
errors = maybeError(errors, additionalSchema == null ?
Json.make("Extra property '" + e.getKey() +
"', schema doesn't allow any properties not explicitly defined:" +
param.toString(maxchars))
: additionalSchema.apply(e.getValue()));
if (param.asJsonMap().size() < min)
errors = maybeError(errors, Json.make("Object " + param.toString(maxchars) +
" has fewer than the permitted " + min + " number of properties."));
if (param.asJsonMap().size() > max)
errors = maybeError(errors, Json.make("Object " + param.toString(maxchars) +
" has more than the permitted " + min + " number of properties."));
return errors;
}
}
class Sequence implements Instruction
{
ArrayList seq = new ArrayList();
public Json apply(Json param)
{
Json errors = null;
for (Instruction I : seq)
errors = maybeError(errors, I.apply(param));
return errors;
}
public Sequence add(Instruction I) { seq.add(I); return this; }
}
class CheckType implements Instruction
{
Json types;
public CheckType(Json types) { this.types = types; }
public Json apply(Json param)
{
String ptype = param.isString() ? "string" :
param.isObject() ? "object" :
param.isArray() ? "array" :
param.isNumber() ? "number" :
param.isNull() ? "null" : "boolean";
for (Json type : types.asJsonList())
if (type.asString().equals(ptype))
return null;
else if (type.asString().equals("integer") &&
param.isNumber() &&
param.asDouble() % 1 == 0)
return null;
return Json.array().add(Json.make("Type mistmatch for " + param.toString(maxchars) +
", allowed types: " + types));
}
}
class CheckEnum implements Instruction
{
Json theenum;
public CheckEnum(Json theenum) { this.theenum = theenum; }
public Json apply(Json param)
{
for (Json option : theenum.asJsonList())
if (param.equals(option))
return null;
return Json.array().add("Element " + param.toString(maxchars) +
" doesn't match any of enumerated possibilities " + theenum);
}
}
class CheckAny implements Instruction
{
ArrayList alternates = new ArrayList();
Json schema;
public Json apply(Json param)
{
for (Instruction I : alternates)
if (I.apply(param) == null)
return null;
return Json.array().add("Element " + param.toString(maxchars) +
" must conform to at least one of available sub-schemas " +
schema.toString(maxchars));
}
}
class CheckOne implements Instruction
{
ArrayList alternates = new ArrayList();
Json schema;
public Json apply(Json param)
{
int matches = 0;
Json errors = Json.array();
for (Instruction I : alternates)
{
Json result = I.apply(param);
if (result == null)
matches++;
else
errors.add(result);
}
if (matches != 1)
{
return Json.array().add("Element " + param.toString(maxchars) +
" must conform to exactly one of available sub-schemas, but not more " +
schema.toString(maxchars)).add(errors);
}
else
return null;
}
}
class CheckNot implements Instruction
{
Instruction I;
Json schema;
public CheckNot(Instruction I, Json schema) { this.I = I; this.schema = schema; }
public Json apply(Json param)
{
if (I.apply(param) != null)
return null;
else
return Json.array().add("Element " + param.toString(maxchars) +
" must NOT conform to the schema " + schema.toString(maxchars));
}
}
class CheckSchemaDependency implements Instruction
{
Instruction schema;
String property;
public CheckSchemaDependency(String property, Instruction schema) { this.property = property; this.schema = schema; }
public Json apply(Json param)
{
if (!param.isObject()) return null;
else if (!param.has(property)) return null;
else return (schema.apply(param));
}
}
class CheckPropertyDependency implements Instruction
{
Json required;
String property;
public CheckPropertyDependency(String property, Json required) { this.property = property; this.required = required; }
public Json apply(Json param)
{
if (!param.isObject()) return null;
if (!param.has(property)) return null;
else
{
Json errors = null;
for (Json p : required.asJsonList())
if (!param.has(p.asString()))
errors = maybeError(errors, Json.make("Conditionally required property " + p +
" missing from object " + param.toString(maxchars)));
return errors;
}
}
}
Instruction compile(Json S, Map compiled)
{
Instruction result = compiled.get(S);
if (result != null)
return result;
Sequence seq = new Sequence();
compiled.put(S, seq);
if (S.has("type") && !S.is("type", "any"))
seq.add(new CheckType(S.at("type").isString() ?
Json.array().add(S.at("type")) : S.at("type")));
if (S.has("enum"))
seq.add(new CheckEnum(S.at("enum")));
if (S.has("allOf"))
{
Sequence sub = new Sequence();
for (Json x : S.at("allOf").asJsonList())
sub.add(compile(x, compiled));
seq.add(sub);
}
if (S.has("anyOf"))
{
CheckAny any = new CheckAny();
any.schema = S.at("anyOf");
for (Json x : any.schema.asJsonList())
any.alternates.add(compile(x, compiled));
seq.add(any);
}
if (S.has("oneOf"))
{
CheckOne any = new CheckOne();
any.schema = S.at("oneOf");
for (Json x : any.schema.asJsonList())
any.alternates.add(compile(x, compiled));
seq.add(any);
}
if (S.has("not"))
seq.add(new CheckNot(compile(S.at("not"), compiled), S.at("not")));
if (S.has("required") && S.at("required").isArray())
{
for (Json p : S.at("required").asJsonList())
seq.add(new CheckPropertyPresent(p.asString()));
}
CheckObject objectCheck = new CheckObject();
if (S.has("properties"))
for (Map.Entry p : S.at("properties").asJsonMap().entrySet())
objectCheck.props.add(objectCheck.new CheckProperty(
p.getKey(), compile(p.getValue(), compiled)));
if (S.has("patternProperties"))
for (Map.Entry p : S.at("patternProperties").asJsonMap().entrySet())
objectCheck.patternProps.add(objectCheck.new CheckPatternProperty(p.getKey(),
compile(p.getValue(), compiled)));
if (S.has("additionalProperties"))
{
if (S.at("additionalProperties").isObject())
objectCheck.additionalSchema = compile(S.at("additionalProperties"), compiled);
else if (!S.at("additionalProperties").asBoolean())
objectCheck.additionalSchema = null; // means no additional properties allowed
}
if (S.has("minProperties"))
objectCheck.min = S.at("minProperties").asInteger();
if (S.has("maxProperties"))
objectCheck.max = S.at("maxProperties").asInteger();
if (!objectCheck.props.isEmpty() || !objectCheck.patternProps.isEmpty() ||
objectCheck.additionalSchema != any ||
objectCheck.min > 0 || objectCheck.max < Integer.MAX_VALUE)
seq.add(objectCheck);
CheckArray arrayCheck = new CheckArray();
if (S.has("items"))
if (S.at("items").isObject())
arrayCheck.schema = compile(S.at("items"), compiled);
else
{
arrayCheck.schemas = new ArrayList();
for (Json s : S.at("items").asJsonList())
arrayCheck.schemas.add(compile(s, compiled));
}
if (S.has("additionalItems"))
if (S.at("additionalItems").isObject())
arrayCheck.additionalSchema = compile(S.at("additionalItems"), compiled);
else if (!S.at("additionalItems").asBoolean())
arrayCheck.additionalSchema = null;
if (S.has("uniqueItems"))
arrayCheck.uniqueitems = S.at("uniqueItems").asBoolean();
if (S.has("minItems"))
arrayCheck.min = S.at("minItems").asInteger();
if (S.has("maxItems"))
arrayCheck.max = S.at("maxItems").asInteger();
if (arrayCheck.schema != null || arrayCheck.schemas != null ||
arrayCheck.additionalSchema != any ||
arrayCheck.uniqueitems != null ||
arrayCheck.max < Integer.MAX_VALUE || arrayCheck.min > 0)
seq.add(arrayCheck);
CheckNumber numberCheck = new CheckNumber();
if (S.has("minimum"))
numberCheck.min = S.at("minimum").asDouble();
if (S.has("maximum"))
numberCheck.max = S.at("maximum").asDouble();
if (S.has("multipleOf"))
numberCheck.multipleOf = S.at("multipleOf").asDouble();
if (S.has("exclusiveMinimum"))
numberCheck.exclusiveMin = S.at("exclusiveMinimum").asBoolean();
if (S.has("exclusiveMaximum"))
numberCheck.exclusiveMax = S.at("exclusiveMaximum").asBoolean();
if (!Double.isNaN(numberCheck.min) || !Double.isNaN(numberCheck.max) || !Double.isNaN(numberCheck.multipleOf))
seq.add(numberCheck);
CheckString stringCheck = new CheckString();
if (S.has("minLength"))
stringCheck.min = S.at("minLength").asInteger();
if (S.has("maxLength"))
stringCheck.max = S.at("maxLength").asInteger();
if (S.has("pattern"))
stringCheck.pattern = Pattern.compile(S.at("pattern").asString());
if (stringCheck.min > 0 || stringCheck.max < Integer.MAX_VALUE || stringCheck.pattern != null)
seq.add(stringCheck);
if (S.has("dependencies"))
for (Map.Entry e : S.at("dependencies").asJsonMap().entrySet())
if (e.getValue().isObject())
seq.add(new CheckSchemaDependency(e.getKey(), compile(e.getValue(), compiled)));
else if (e.getValue().isArray())
seq.add(new CheckPropertyDependency(e.getKey(), e.getValue()));
else
seq.add(new CheckPropertyDependency(e.getKey(), Json.array(e.getValue())));
result = seq.seq.size() == 1 ? seq.seq.get(0) : seq;
compiled.put(S, result);
return result;
}
int maxchars = 50;
URI uri;
Json theschema;
Instruction start;
DefaultSchema(URI uri, Json theschema, Function relativeReferenceResolver)
{
try
{
this.uri = uri == null ? new URI("") : uri;
if (relativeReferenceResolver == null)
relativeReferenceResolver = new Function() { public Json apply(URI docuri) {
try { return Json.read(fetchContent(docuri.toURL())); }
catch(Exception ex) { throw new RuntimeException(ex); }
}};
this.theschema = theschema.dup();
this.theschema = expandReferences(this.theschema,
this.theschema,
this.uri,
new HashMap(),
new IdentityHashMap(),
relativeReferenceResolver);
}
catch (Exception ex) { throw new RuntimeException(ex); }
this.start = compile(this.theschema, new IdentityHashMap());
}
public Json validate(Json document)
{
Json result = Json.object("ok", true);
Json errors = start.apply(document);
return errors == null ? result : result.set("errors", errors).set("ok", false);
}
public Json toJson()
{
return theschema;
}
public Json generate(Json options)
{
// TODO...
return Json.nil();
}
}
public static Schema schema(Json S)
{
return new DefaultSchema(null, S, null);
}
public static Schema schema(URI uri)
{
return schema(uri, null);
}
public static Schema schema(URI uri, Function relativeReferenceResolver)
{
try { return new DefaultSchema(uri, Json.read(Json.fetchContent(uri.toURL())), relativeReferenceResolver); }
catch (Exception ex) { throw new RuntimeException(ex); }
}
public static Schema schema(Json S, URI uri)
{
return new DefaultSchema(uri, S, null);
}
public static class DefaultFactory implements Factory
{
public Json nil() { return new NullJson(); }
public Json bool(boolean x) { return new BooleanJson(x ? Boolean.TRUE : Boolean.FALSE, null); }
public Json string(String x) { return new StringJson(x, null); }
public Json number(Number x) { return new NumberJson(x, null); }
public Json array() { return new ArrayJson(); }
public Json object() { return new ObjectJson(); }
public Json make(Object anything)
{
if (anything == null)
return nil();
else if (anything instanceof Json)
return (Json)anything;
else if (anything instanceof String)
return factory().string((String)anything);
else if (anything instanceof Collection>)
{
Json L = array();
for (Object x : (Collection>)anything)
L.add(factory().make(x));
return L;
}
else if (anything instanceof Map,?>)
{
Json O = object();
for (Map.Entry,?> x : ((Map,?>)anything).entrySet())
O.set(x.getKey().toString(), factory().make(x.getValue()));
return O;
}
else if (anything instanceof Boolean)
return factory().bool((Boolean)anything);
else if (anything instanceof Number)
return factory().number((Number)anything);
else if (anything.getClass().isArray())
{
Class> comp = anything.getClass().getComponentType();
if (!comp.isPrimitive())
return Json.array((Object[])anything);
Json A = array();
if (boolean.class == comp)
for (boolean b : (boolean[])anything) A.add(b);
else if (byte.class == comp)
for (byte b : (byte[])anything) A.add(b);
else if (char.class == comp)
for (char b : (char[])anything) A.add(b);
else if (short.class == comp)
for (short b : (short[])anything) A.add(b);
else if (int.class == comp)
for (int b : (int[])anything) A.add(b);
else if (long.class == comp)
for (long b : (long[])anything) A.add(b);
else if (float.class == comp)
for (float b : (float[])anything) A.add(b);
else if (double.class == comp)
for (double b : (double[])anything) A.add(b);
return A;
}
else
throw new IllegalArgumentException("Don't know how to convert to Json : " + anything);
}
}
public static final Factory defaultFactory = new DefaultFactory();
private static Factory globalFactory = defaultFactory;
// TODO: maybe use initialValue thread-local method to attach global factory by default here...
private static ThreadLocal threadFactory = new ThreadLocal();
/**
* Return the {@link Factory} currently in effect. This is the factory that the {@link #make(Object)} method
* will dispatch on upon determining the type of its argument. If you already know the type
* of element to construct, you can avoid the type introspection implicit to the make method
* and call the factory directly. This will result in an optimization.
*
* @return the factory
*/
public static Factory factory()
{
Factory f = threadFactory.get();
return f != null ? f : globalFactory;
}
/**
*
* Specify a global Json {@link Factory} to be used by all threads that don't have a
* specific thread-local factory attached to them.
*
*
* @param factory The new global factory
*/
public static void setGlobalFactory(Factory factory) { globalFactory = factory; }
/**
*
* Attach a thread-local Json {@link Factory} to be used specifically by this thread. Thread-local
* Json factories are the only means to have different {@link Factory} implementations used simultaneously
* in the same application (well, more accurately, the same ClassLoader).
*
*
* @param factory the new thread local factory
*/
public static void attachFactory(Factory factory) { threadFactory.set(factory); }
/**
*
* Clear the thread-local factory previously attached to this thread via the
* {@link #attachFactory(Factory)} method. The global factory takes effect after
* a call to this method.
*
*/
public static void detachFactory() { threadFactory.remove(); }
/**
*
* Parse a JSON entity from its string representation.
*
*
* @param jsonAsString A valid JSON representation as per the json.org
* grammar. Cannot be null
.
* @return The JSON entity parsed: an object, array, string, number or boolean, or null. Note that
* this method will never return the actual Java null
.
*/
public static Json read(String jsonAsString) { return (Json)new Reader().read(jsonAsString); }
/**
*
* Parse a JSON entity from a URL
.
*
*
* @param location A valid URL where to load a JSON document from. Cannot be null
.
* @return The JSON entity parsed: an object, array, string, number or boolean, or null. Note that
* this method will never return the actual Java null
.
*/
public static Json read(URL location) { return (Json)new Reader().read(fetchContent(location)); }
/**
*
* Parse a JSON entity from a {@link CharacterIterator}.
*
* @param it A character iterator.
* @return the parsed JSON element
* @see #read(String)
*/
public static Json read(CharacterIterator it) { return (Json)new Reader().read(it); }
/**
* @return the null Json
instance.
*/
public static Json nil() { return factory().nil(); }
/**
* @return a newly constructed, empty JSON object.
*/
public static Json object() { return factory().object(); }
/**
* Return a new JSON object initialized from the passed list of
* name/value pairs. The number of arguments must
* be even. Each argument at an even position is taken to be a name
* for the following value. The name arguments are normally of type
* Java String, but they can be of any other type having an appropriate
* toString
method. Each value is first converted
* to a Json
instance using the {@link #make(Object)} method.
*
* @param args A sequence of name value pairs.
* @return the new JSON object.
*/
public static Json object(Object...args)
{
Json j = object();
if (args.length % 2 != 0)
throw new IllegalArgumentException("An even number of arguments is expected.");
for (int i = 0; i < args.length; i++)
j.set(args[i].toString(), factory().make(args[++i]));
return j;
}
/**
* @return a new constructed, empty JSON array.
*/
public static Json array() { return factory().array(); }
/**
* Return a new JSON array filled up with the list of arguments.
*
* @param args The initial content of the array.
* @return the new JSON array
*/
public static Json array(Object...args)
{
Json A = array();
for (Object x : args)
A.add(factory().make(x));
return A;
}
/**
*
* Exposes some internal methods that are useful for {@link org.sharegov.mjson.Json.Factory} implementations
* or other extension/layers of the library.
*
*
* @author Borislav Iordanov
*
*/
public static class help
{
/**
*
* Perform JSON escaping so that ", <, >, etc. characters are properly encoded in the
* JSON string representation before returning to the client code. This is useful when
* serializing property names or string values.
*
*/
public static String escape(String string) { return escaper.escapeJsonString(string); }
/**
*
* Given a JSON Pointer, as per RFC 6901, return the nested JSON value within
* the element
parameter.
*
*/
public static Json resolvePointer(String pointer, Json element) { return Json.resolvePointer(pointer, element); }
}
static class JsonSingleValueIterator implements Iterator {
private boolean retrieved = false;
@Override
public boolean hasNext() {
return !retrieved;
}
@Override
public Json next() {
retrieved = true;
return null;
}
@Override
public void remove() {
}
}
/**
*
* Convert an arbitrary Java instance to a {@link Json} instance.
*
*
*
* Maps, Collections and arrays are recursively copied where each of
* their elements concerted into Json
instances as well. The keys
* of a {@link Map} parameter are normally strings, but anything with a meaningful
* toString
implementation will work as well.
*
*
* @param anything Any Java object that the current JSON factory in effect is capable of handling.
* @return The Json
. This method will never return null
. It will
* throw an {@link IllegalArgumentException} if it doesn't know how to convert the argument
* to a Json
instance.
* @throws IllegalArgumentException when the concrete type of the parameter is
* unknown.
*/
public static Json make(Object anything)
{
return factory().make(anything);
}
// end of static utility method section
Json enclosing = null;
protected Json() { }
protected Json(Json enclosing) { this.enclosing = enclosing; }
/**
* Return a string representation of this
that does
* not exceed a certain maximum length. This is useful in constructing
* error messages or any other place where only a "preview" of the
* JSON element should be displayed. Some JSON structures can get
* very large and this method will help avoid string serializing
* the whole of them.
* @param maxCharacters The maximum number of characters for
* the string representation.
* @return The string representation of this object.
*/
public String toString(int maxCharacters) { return toString(); }
/**
* Explicitly set the parent of this element. The parent is presumably an array
* or an object. Normally, there's no need to call this method as the parent is
* automatically set by the framework. You may need to call it however, if you implement
* your own {@link Factory} with your own implementations of the Json types.
*
*
* @param enclosing The parent element.
*/
public void attachTo(Json enclosing) { this.enclosing = enclosing; }
/**
* @return the Json
entity, if any, enclosing this
* Json
. The returned value can be null
or
* a Json
object or list, but not one of the primitive types.
*/
public final Json up() { return enclosing; }
/**
* @return a clone (a duplicate) of this Json
entity. Note that cloning
* is deep if array and objects. Primitives are also cloned, even though their values are immutable
* because the new enclosing entity (the result of the {@link #up()} method) may be different.
* since they are immutable.
*/
public Json dup() { return this; }
/**
* Return the Json
element at the specified index of this
* Json
array. This method applies only to Json arrays.
*
*
* @param index The index of the desired element.
* @return The JSON element at the specified index in this array.
*/
public Json at(int index) { throw new UnsupportedOperationException(); }
/**
*
* Return the specified property of a Json
object or null
* if there's no such property. This method applies only to Json objects.
*
* @param The property name.
* @return The JSON element that is the value of that property.
*/
public Json at(String property) { throw new UnsupportedOperationException(); }
/**
*
* Return the specified property of a Json
object if it exists.
* If it doesn't, then create a new property with value the def
* parameter and return that parameter.
*
*
* @param property The property to return.
* @param def The default value to set and return in case the property doesn't exist.
*/
public final Json at(String property, Json def)
{
Json x = at(property);
if (x == null)
{
// set(property, def);
return def;
}
else
return x;
}
/**
*
* Return the specified property of a Json
object if it exists.
* If it doesn't, then create a new property with value the def
* parameter and return that parameter.
*
*
* @param property The property to return.
* @param def The default value to set and return in case the property doesn't exist.
*/
public final Json at(String property, Object def)
{
return at(property, make(def));
}
/**
*
* Return true if this Json
object has the specified property
* and false otherwise.
*
*
* @param property The name of the property.
*/
public boolean has(String property) { throw new UnsupportedOperationException(); }
/**
*
* Return true
if and only if this Json
object has a property with
* the specified value. In particular, if the object has no such property false
is returned.
*
*
* @param property The property name.
* @param value The value to compare with. Comparison is done via the equals method.
* If the value is not an instance of Json
, it is first converted to
* such an instance.
* @return
*/
public boolean is(String property, Object value) { throw new UnsupportedOperationException(); }
/**
*
* Return true
if and only if this Json
array has an element with
* the specified value at the specified index. In particular, if the array has no element at
* this index, false
is returned.
*
*
* @param index The 0-based index of the element in a JSON array.
* @param value The value to compare with. Comparison is done via the equals method.
* If the value is not an instance of Json
, it is first converted to
* such an instance.
* @return
*/
public boolean is(int index, Object value) { throw new UnsupportedOperationException(); }
/**
*
* Add the specified Json
element to this array.
*
*
* @return this
*/
public Json add(Json el) { throw new UnsupportedOperationException(); }
/**
*
* Add an arbitrary Java object to this Json
array. The object
* is first converted to a Json
instance by calling the static
* {@link #make} method.
*
*
* @param anything Any Java object that can be converted to a Json instance.
* @return this
*/
public final Json add(Object anything) { return add(make(anything)); }
/**
*
* Remove the specified property from a Json
object and return
* that property.
*
*
* @param property The property to be removed.
* @return The property value or null
if the object didn't have such
* a property to begin with.
*/
public Json atDel(String property) { throw new UnsupportedOperationException(); }
/**
*
* Remove the element at the specified index from a Json
array and return
* that element.
*
*
* @param index The index of the element to delete.
* @return The element value.
*/
public Json atDel(int index) { throw new UnsupportedOperationException(); }
/**
*
* Delete the specified property from a Json
object.
*
*
* @param property The property to be removed.
* @return this
*/
public Json delAt(String property) { throw new UnsupportedOperationException(); }
/**
*
* Remove the element at the specified index from a Json
array.
*
*
* @param index The index of the element to delete.
* @return this
*/
public Json delAt(int index) { throw new UnsupportedOperationException(); }
/**
*
* Remove the specified element from a Json
array.
*
*
* @param el The element to delete.
* @return this
*/
public Json remove(Json el) { throw new UnsupportedOperationException(); }
/**
*
* Remove the specified Java object (converted to a Json instance)
* from a Json
array. This is equivalent to
* remove({@link #make(Object)})
.
*
*
* @param anything The object to delete.
* @return this
*/
public final Json remove(Object anything) { return remove(make(anything)); }
/**
*
* Set a Json
objects's property.
*
*
* @param property The property name.
* @param value The value of the property.
* @return this
*/
public Json set(String property, Json value) { throw new UnsupportedOperationException(); }
/**
*
* Set a Json
objects's property.
*
*
* @param property The property name.
* @param value The value of the property, converted to a Json
representation
* with {@link #make}.
* @return this
*/
public final Json set(String property, Object value) { return set(property, make(value)); }
/**
*
* Change the value of a JSON array element. This must be an array.
*
* @param index 0-based index of the element in the array.
* @param value the new value of the element
* @return this
*/
public Json set(int index, Object value) { throw new UnsupportedOperationException(); }
/**
*
* Combine this object or array with the passed in object or array. The types of
* this
and the object
argument must match. If both are
* Json
objects, all properties of the parameter are added to this
.
* If both are arrays, all elements of the parameter are appended to this
*
* @param object The object or array whose properties or elements must be added to this
* Json object or array.
* @param options A sequence of options that governs the merging process.
* @return this
*/
public Json with(Json object, Json[]options) { throw new UnsupportedOperationException(); }
/**
* Same as {}@link #with(Json,Json...options)}
with each option
* argument converted to Json
first.
*/
public Json with(Json object, Object...options)
{
Json [] jopts = new Json[options.length];
for (int i = 0; i < jopts.length; i++)
jopts[i] = make(options[i]);
return with(object, jopts);
}
/**
* @return the underlying value of this Json
entity. The actual value will
* be a Java Boolean, String, Number, Map, List or null. For complex entities (objects
* or arrays), the method will perform a deep copy and extra underlying values recursively
* for all nested elements.
*/
public Object getValue() { throw new UnsupportedOperationException(); }
/**
* @return the boolean value of a boolean Json
instance. Call
* {@link #isBoolean()} first if you're not sure this instance is indeed a
* boolean.
*/
public boolean asBoolean() { throw new UnsupportedOperationException(); }
/**
* @return the string value of a string Json
instance. Call
* {@link #isString()} first if you're not sure this instance is indeed a
* string.
*/
public String asString() { throw new UnsupportedOperationException(); }
/**
* @return the integer value of a number Json
instance. Call
* {@link #isNumber()} first if you're not sure this instance is indeed a
* number.
*/
public int asInteger() { throw new UnsupportedOperationException(); }
/**
* @return the float value of a float Json
instance. Call
* {@link #isNumber()} first if you're not sure this instance is indeed a
* number.
*/
public float asFloat() { throw new UnsupportedOperationException(); }
/**
* @return the double value of a number Json
instance. Call
* {@link #isNumber()} first if you're not sure this instance is indeed a
* number.
*/
public double asDouble() { throw new UnsupportedOperationException(); }
/**
* @return the long value of a number Json
instance. Call
* {@link #isNumber()} first if you're not sure this instance is indeed a
* number.
*/
public long asLong() { throw new UnsupportedOperationException(); }
/**
* @return the short value of a number Json
instance. Call
* {@link #isNumber()} first if you're not sure this instance is indeed a
* number.
*/
public short asShort() { throw new UnsupportedOperationException(); }
/**
* @return the byte value of a number Json
instance. Call
* {@link #isNumber()} first if you're not sure this instance is indeed a
* number.
*/
public byte asByte() { throw new UnsupportedOperationException(); }
/**
* @return the first character of a string Json
instance. Call
* {@link #isString()} first if you're not sure this instance is indeed a
* string.
*/
public char asChar() { throw new UnsupportedOperationException(); }
/**
* @return a map of the properties of an object Json
instance. The map
* is a clone of the object and can be modified safely without affecting it. Call
* {@link #isObject()} first if you're not sure this instance is indeed a
* Json
object.
*/
public Map asMap() { throw new UnsupportedOperationException(); }
/**
* @return the underlying map of properties of a Json
object. The returned
* map is the actual object representation so any modifications to it are modifications
* of the Json
object itself. Call
* {@link #isObject()} first if you're not sure this instance is indeed a
* Json
object.
*/
public Map asJsonMap() { throw new UnsupportedOperationException(); }
/**
* @return a list of the elements of a Json
array. The list is a clone
* of the array and can be modified safely without affecting it. Call
* {@link #isArray()} first if you're not sure this instance is indeed a
* Json
array.
*/
public List