pax_global_header 0000666 0000000 0000000 00000000064 12773500750 0014520 g ustar 00root root 0000000 0000000 52 comment=b2313f9f8ab4db3ac7e965f9db12bccc1ad8d552
mjson-1.4.0/ 0000775 0000000 0000000 00000000000 12773500750 0012650 5 ustar 00root root 0000000 0000000 mjson-1.4.0/.gitignore 0000664 0000000 0000000 00000000116 12773500750 0014636 0 ustar 00root root 0000000 0000000 # Maven / Eclipse
/target/
.classpath
.project
.settings
# Idea
.idea
*.iml
mjson-1.4.0/LICENSE.txt 0000664 0000000 0000000 00000026136 12773500750 0014503 0 ustar 00root root 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
mjson-1.4.0/README.md 0000664 0000000 0000000 00000004017 12773500750 0014131 0 ustar 00root root 0000000 0000000 mJson is an extremely lightweight Java JSON library with a very concise API. The source code is a single Java file. The license is Apache 2.0. Because of its tiny size, it's well-suited for any application aiming at a small footprint such as mobile/Android applications.
It was originally developed in the context of the [OpenCiRM](https://github.com/sharegov/opencirm) project. There is a graph database based persistent layer for mJson implemented at the [HyperGraphDB Project](http://hypergraphdb.org/learn?page=Json&project=hypergraphdb). This means you can transparently persist and query JSON documents like in document-oriented databases (MongoDB, CouchDB), but you don't have split documents into separate collection or create special purposes indices since all documents and properties are automatically interlinked.
### Features
* Full support for [JSON Schema Draft 4](http://json-schema.org/) validation
* Single universal type - everything is a `Json`, no type casting
* Single factory method, no new operators, just call `Json.make(anything here)`
* Fast, hand-coded parsing
* Designed as a general purpose data structure for use in Java
* Parent pointers and `up` method to traverse the JSON structure
* Concise methods to read (`Json.at`), modify (`Json.set`, `Json.add`), duplicate (`Json.dup`), merge (`Json.with`)
* Methods for type-check (e.g. `Json.isString()`) and access to underlying Java value (e.g. `Json.asString()`)
* Method chaining
* Pluggable factory to build your own support for arbitrary Java<->Json mapping
* 1 Java file is the whole library with no external dependencies
### API Tour
Go see a **[Complete Tour of the API](https://github.com/bolerio/mjson/wiki/A-Tour-of-the-API)**
Read my tutorial blog on **[JSON Schema](http://www.kobrix.blogspot.com/2014/09/jayson-skima-validating-javascript.html)**
### Wish List
(get in touch if you want to help!)
1. Traversal API, with pattern-matching
2. Extend JSON Schema support for template generation
**[Goto mJson Official Website](http://bolerio.github.io/mjson/)**
mjson-1.4.0/changelog.txt 0000664 0000000 0000000 00000000105 12773500750 0015334 0 ustar 00root root 0000000 0000000 1.3 Changes:
- Check for null property names in ObjectJson.set
mjson-1.4.0/pom.xml 0000664 0000000 0000000 00000013275 12773500750 0014175 0 ustar 00root root 0000000 0000000 4.0.0org.sonatype.ossoss-parent7bundle${project.groupId}-${project.artifactId};singleton:=truemjson${packageit}org.sharegovmjsonMinimal JSON Library1.4.0http://sharegov.org/mjsonA concise, loosely typed Java API for JsonThe Apache Software License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0.txtrepoborisBorislav Iordanovborislav.iordanov@gmail.comossrhhttps://oss.sonatype.org/content/repositories/snapshotsossrhhttps://oss.sonatype.org/service/local/staging/deploy/maven2/targettarget/classes${project.artifactId}-${project.version}target/test-classessrc/javaorg.apache.maven.pluginsmaven-compiler-plugin2.0.21.71.7org.apache.maven.pluginsmaven-source-pluginattach-sourcesjarorg.apache.maven.pluginsmaven-javadoc-plugin-Xdoclint:noneattach-javadocsjarorg.apache.maven.pluginsmaven-deploy-plugintrueorg.sonatype.pluginsnexus-staging-maven-pluginhttps://oss.sonatype.org/ossrhdefault-deploydeploydeployorg.apache.maven.pluginsmaven-surefire-plugin2.17src/test/java${project.basedir}/src/test/resourcesorg.apache.felixmaven-bundle-plugin2.5.3trueMETA-INF${project.groupId}-${project.artifactId};singleton:=true${project.name}${project.version}mjsonbiz.aQute.bndbndlib2.4.0junitjunit4.12
mjson-1.4.0/src/ 0000775 0000000 0000000 00000000000 12773500750 0013437 5 ustar 00root root 0000000 0000000 mjson-1.4.0/src/java/ 0000775 0000000 0000000 00000000000 12773500750 0014360 5 ustar 00root root 0000000 0000000 mjson-1.4.0/src/java/mjson/ 0000775 0000000 0000000 00000000000 12773500750 0015506 5 ustar 00root root 0000000 0000000 mjson-1.4.0/src/java/mjson/Json.java 0000775 0000000 0000000 00000317051 12773500750 0017274 0 ustar 00root root 0000000 0000000 /*
* 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:
*
* 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:
*
* @author Borislav Iordanov
* @version 1.4
*/
public class Json implements java.io.Serializable
{
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
* 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.
*
Possible options are: ignoreDefaults:true|false.
*
* @return A newly created Json conforming to this schema.
*/
//Json generate(Json options);
}
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 Json.topnull; }
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 topnull;
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.
*
* 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