/*
* Copyright 2003-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package groovy.json;
import groovy.json.internal.JsonFastParser;
import groovy.json.internal.JsonParserCharArray;
import groovy.json.internal.JsonParserLax;
import groovy.json.internal.JsonParserUsingCharacterSource;
import org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport;
import org.codehaus.groovy.runtime.ResourceGroovyMethods;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URL;
import java.util.*;
/**
* This has the same interface as the original JsonSlurper written for version 1.8.0, but its
* implementation has completely changed. It is now up to 20x faster than before, and its speed
* competes and often substantially exceeds popular common JSON parsers circa Jan, 2014.
* <p />
* JSON slurper parses text or reader content into a data structure of lists and maps.
* <p>
* Example usage:
* <code><pre>
* def slurper = new JsonSlurper()
* def result = slurper.parseText('{"person":{"name":"Guillaume","age":33,"pets":["dog","cat"]}}')
*
* assert result.person.name == "Guillaume"
* assert result.person.age == 33
* assert result.person.pets.size() == 2
* assert result.person.pets[0] == "dog"
* assert result.person.pets[1] == "cat"
* </pre></code>
*
* JsonSlurper can use several types of JSON parsers. Please read the documentation for
* JsonParserType. There are relaxed mode parsers, large file parser, and index overlay parsers.
* Don't worry, it is all groovy. JsonSlurper will just work, but understanding the different parser
* types may allow you to drastically improve the performance of your JSON parsing.
* <p />
*
* Index overlay parsers (INDEX_OVERLAY and LAX) are the fastest JSON parsers.
* However they are not the default for a good reason.
* Index overlay parsers has pointers (indexes really) to original char buffer.
* Care must be used if putting parsed maps into a long term cache as members of map
* maybe index overlay objects pointing to original buffer.
* You can mitigate these risks by using chop and lazy chop properties.
* <p />
* Chop eagerly dices up the buffer so each Value element points to a small copy of the original buffer.
* <p />
* Lazy Chop dices up the buffer when a list get or map get is called so if an GPath expression or
* such is applied.
* <p />
* You do not need chop or lazy chop if you are NOT putting the map into a long term cache.
* You do not need chop or lazy chop if you are doing object de-serialization.
* Recommendation is to use INDEX_OVERLAY for JSON buffers under 2MB.
* The maxSizeForInMemory is set to 2MB and any file over 2MB will use a parser designed for
* large files, which is slower than the INDEX_OVERLAY, LAX, and CHAR_BUFFER parsers, but
* faster than most commonly used JSON parsers on the JVM for most use cases circa January 2014.
* <p />
* To enable the INDEX_OVERLAY parser do this:
*
* <code><pre>
* parser = new JsonSlurper().setType( JsonParserType.INDEX_OVERLAY );
* </pre></code>
*
* @see groovy.json.JsonParserType
*
* @author Guillaume Laforge
* @author Rick Hightower
* @since 1.8.0
*/
public class JsonSlurper {
private int maxSizeForInMemory = 2000000;
private boolean chop = false;
private boolean lazyChop = true;
private boolean checkDates = true;
private JsonParserType type = JsonParserType.CHAR_BUFFER;
/**
* Max size before Slurper starts to use windowing buffer parser.
* @return size of file/buffer
* @since 2.3
*/
public int getMaxSizeForInMemory() {
return maxSizeForInMemory;
}
/**
* Max size before Slurper starts to use windowing buffer parser.
* @since 2.3
* @return JsonSlurper
*/
public JsonSlurper setMaxSizeForInMemory( int maxSizeForInMemory ) {
this.maxSizeForInMemory = maxSizeForInMemory;
return this;
}
/** Parser type.
* @since 2.3
* @see groovy.json.JsonParserType
* @return type
*/
public JsonParserType getType() {
return type;
}
/** Parser type.
* @since 2.3
* @see groovy.json.JsonParserType
* @return JsonSlurper
*/
public JsonSlurper setType( JsonParserType type ) {
this.type = type;
return this;
}
/** Turns on buffer chopping for index overlay.
* @since 2.3
* @see groovy.json.JsonParserType
* @return chop on or off
*/
public boolean isChop() {
return chop;
}
/** Turns on buffer chopping for index overlay.
* @since 2.3
* @see groovy.json.JsonParserType
* @return JsonSlurper
*/
public JsonSlurper setChop( boolean chop ) {
this.chop = chop;
return this;
}
/** Turns on buffer lazy chopping for index overlay.
* @see groovy.json.JsonParserType
* @return on or off
* @since 2.3
*/
public boolean isLazyChop() {
return lazyChop;
}
/** Turns on buffer lazy chopping for index overlay.
* @see groovy.json.JsonParserType
* @return JsonSlurper
* @since 2.3
*/
public JsonSlurper setLazyChop( boolean lazyChop ) {
this.lazyChop = lazyChop;
return this;
}
/**
* Determine if slurper will automatically parse strings it recognizes as dates. Index overlay only.
* @return on or off
* @since 2.3
*/
public boolean isCheckDates() {
return checkDates;
}
/**
* Determine if slurper will automatically parse strings it recognizes as dates. Index overlay only.
* @return on or off
* @since 2.3
*/
public JsonSlurper setCheckDates( boolean checkDates ) {
this.checkDates = checkDates;
return this;
}
/**
* Parse a text representation of a JSON data structure
*
* @param text JSON text to parse
* @return a data structure of lists and maps
*/
public Object parseText(String text) {
if (text == null || "".equals ( text )) {
throw new IllegalArgumentException ( "Text must not be null" );
}
return createParser().parse( text );
}
/**
* Parse a JSON data structure from content from a reader
*
* @param reader reader over a JSON content
* @return a data structure of lists and maps
*/
public Object parse(Reader reader) {
if (reader == null ) {
throw new IllegalArgumentException ( "Reader must not be null" );
}
Object content;
JsonParser parser = createParser();
content = parser.parse(reader);
return content;
}
/**
* Parse a JSON data structure from content from an inputStream
*
* @param inputStream stream over a JSON content
* @return a data structure of lists and maps
* @since 2.3
*/
public Object parse(InputStream inputStream) {
if (inputStream == null ) {
throw new IllegalArgumentException ( "inputStream must not be null" );
}
Object content;
JsonParser parser = createParser();
content = parser.parse( inputStream );
return content;
}
/**
* Parse a JSON data structure from content from an inputStream
*
* @param inputStream stream over a JSON content
* @param charset charset
* @return a data structure of lists and maps
* @since 2.3
*/
public Object parse(InputStream inputStream, String charset) {
if (inputStream == null ) {
throw new IllegalArgumentException ( "inputStream must not be null" );
}
if ( charset == null ) {
throw new IllegalArgumentException ( "charset must not be null" );
}
Object content;
content = createParser().parse(inputStream, charset);
return content;
}
/**
* Parse a JSON data structure from content from a byte array.
*
* @param bytes buffer of JSON content
* @param charset charset
* @return a data structure of lists and maps
* @since 2.3
*/
public Object parse(byte [] bytes, String charset) {
if ( bytes == null ) {
throw new IllegalArgumentException ( "bytes must not be null" );
}
if ( charset == null ) {
throw new IllegalArgumentException ( "charset must not be null" );
}
Object content;
content = createParser().parse(bytes, charset);
return content;
}
/**
* Parse a JSON data structure from content from a byte array.
*
* @param bytes buffer of JSON content
* @return a data structure of lists and maps
* @since 2.3
*/
public Object parse(byte [] bytes) {
if ( bytes == null ) {
throw new IllegalArgumentException ( "bytes must not be null" );
}
Object content;
content = createParser().parse(bytes);
return content;
}
/**
* Parse a JSON data structure from content from a char array.
*
* @param chars buffer of JSON content
* @return a data structure of lists and maps
* @since 2.3
*/
public Object parse(char [] chars) {
if ( chars == null ) {
throw new IllegalArgumentException ( "chars must not be null" );
}
Object content;
content = createParser().parse(chars);
return content;
}
private JsonParser createParser() {
switch (type) {
case LAX:
return new JsonParserLax(false, chop, lazyChop, checkDates);
case CHAR_BUFFER:
return new JsonParserCharArray();
case CHARACTER_SOURCE:
return new JsonParserUsingCharacterSource();
case INDEX_OVERLAY:
return new JsonFastParser(false, chop, lazyChop, checkDates);
default:
return new JsonParserCharArray();
}
}
/**
* Parse a JSON data structure from content within a given File.
*
* @param file File containing JSON content
* @return a data structure of lists and maps
* @since 2.2.0
*/
public Object parse(File file) {
return parseFile(file, null);
}
/**
* Parse a JSON data structure from content within a given File.
*
* @param file File containing JSON content
* @param charset the charset for this File
* @return a data structure of lists and maps
* @since 2.2.0
*/
public Object parse(File file, String charset) {
return parseFile(file, charset);
}
private Object parseFile(File file, String charset) {
if (file.length() < maxSizeForInMemory) {
return createParser().parse(file, charset);
} else {
return new JsonParserUsingCharacterSource().parse ( file, charset );
}
}
/**
* Parse a JSON data structure from content at a given URL.
*
* @param url URL containing JSON content
* @return a data structure of lists and maps
* @since 2.2.0
*/
public Object parse(URL url) {
return parseURL(url, null);
}
/**
* Parse a JSON data structure from content at a given URL.
*
* @param url URL containing JSON content
* @param params connection parameters
* @return a data structure of lists and maps
* @since 2.2.0
*/
public Object parse(URL url, Map params) {
return parseURL(url, params);
}
/**
* Parse a JSON data structure from content at a given URL. Convenience variant when using Groovy named parameters for the connection params.
*
* @param params connection parameters
* @param url URL containing JSON content
* @return a data structure of lists and maps
* @since 2.2.0
*/
public Object parse(Map params, URL url) {
return parseURL(url, params);
}
private Object parseURL(URL url, Map params) {
Reader reader = null;
try {
if (params == null || params.isEmpty()) {
reader = ResourceGroovyMethods.newReader(url);
} else {
reader = ResourceGroovyMethods.newReader(url, params);
}
return createParser ().parse ( reader );
} catch(IOException ioe) {
throw new JsonException("Unable to process url: " + url.toString(), ioe);
} finally {
if (reader != null) {
DefaultGroovyMethodsSupport.closeWithWarning(reader);
}
}
}
/**
* Parse a JSON data structure from content at a given URL.
*
* @param url URL containing JSON content
* @param charset the charset for this File
* @return a data structure of lists and maps
* @since 2.2.0
*/
public Object parse(URL url, String charset) {
return parseURL(url, null, charset);
}
/**
* Parse a JSON data structure from content at a given URL.
*
* @param url URL containing JSON content
* @param params connection parameters
* @param charset the charset for this File
* @return a data structure of lists and maps
* @since 2.2.0
*/
public Object parse(URL url, Map params, String charset) {
return parseURL(url, params, charset);
}
/**
* Parse a JSON data structure from content at a given URL. Convenience variant when using Groovy named parameters for the connection params.
*
* @param params connection parameters
* @param url URL containing JSON content
* @param charset the charset for this File
* @return a data structure of lists and maps
* @since 2.2.0
*/
public Object parse(Map params, URL url, String charset) {
return parseURL(url, params, charset);
}
private Object parseURL(URL url, Map params, String charset) {
Reader reader = null;
try {
if (params == null || params.isEmpty()) {
reader = ResourceGroovyMethods.newReader(url, charset);
} else {
reader = ResourceGroovyMethods.newReader(url, params, charset);
}
return parse(reader);
} catch(IOException ioe) {
throw new JsonException("Unable to process url: " + url.toString(), ioe);
} finally {
if (reader != null) {
DefaultGroovyMethodsSupport.closeWithWarning(reader);
}
}
}
}