groovy.json.JsonSlurper Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.transform.NamedParam;
import org.apache.groovy.json.internal.JsonFastParser;
import org.apache.groovy.json.internal.JsonParserCharArray;
import org.apache.groovy.json.internal.JsonParserLax;
import org.apache.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.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
/**
* 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.
*
* JSON slurper parses text or reader content into a data structure of lists and maps.
*
* Example usage:
*
* def slurper = new groovy.json.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"
*
*
* 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.
*
*
* 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.
*
* Chop eagerly dices up the buffer so each Value element points to a small copy of the original buffer.
*
* Lazy Chop dices up the buffer when a list get or map get is called so if an GPath expression or
* such is applied.
*
* 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.
*
* To enable the INDEX_OVERLAY parser do this:
*
*
* parser = new JsonSlurper().setType(JsonParserType.INDEX_OVERLAY);
*
*
* @see groovy.json.JsonParserType
* @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 || text.isEmpty()) {
throw new IllegalArgumentException("Text must not be null or empty");
}
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 Path.
*
* @param path {@link Path} containing JSON content
* @return a data structure of lists and maps
*/
public Object parse(Path path) throws IOException {
return parse(Files.newInputStream(path));
}
/**
* Parse a JSON data structure from content within a given Path.
*
* @param path {@link Path} containing JSON content
* @param charset the charset for this File
* @return a data structure of lists and maps
*/
public Object parse(Path path, String charset) throws IOException {
return parse(Files.newInputStream(path), charset);
}
/**
* 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(
@NamedParam(value = "connectTimeout", type = Integer.class)
@NamedParam(value = "readTimeout", type = Integer.class)
@NamedParam(value = "useCaches", type = Boolean.class)
@NamedParam(value = "allowUserInteraction", type = Boolean.class)
@NamedParam(value = "requestProperties", type = Map.class)
Map params,
URL url
) {
return parseURL(url, params);
}
private Object parseURL(
URL url,
@NamedParam(value = "connectTimeout", type = Integer.class)
@NamedParam(value = "readTimeout", type = Integer.class)
@NamedParam(value = "useCaches", type = Boolean.class)
@NamedParam(value = "allowUserInteraction", type = Boolean.class)
@NamedParam(value = "requestProperties", type = Map.class)
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);
}
}
}
}