/* * 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.lang.Closure; import groovy.lang.GroovyObjectSupport; import groovy.lang.Writable; import java.io.IOException; import java.io.Writer; import java.util.*; /** * A builder for creating JSON payloads. * <p> * This builder supports the usual builder syntax made of nested method calls and closures, * but also some specific aspects of JSON data structures, such as list of values, etc. * Please make sure to have a look at the various methods provided by this builder * to be able to learn about the various possibilities of usage. * <p> * Example: * <pre><code class="groovyTestCase"> * def builder = new groovy.json.JsonBuilder() * def root = builder.people { * person { * firstName 'Guillame' * lastName 'Laforge' * // Named arguments are valid values for objects too * address( * city: 'Paris', * country: 'France', * zip: 12345, * ) * married true * // a list of values * conferences 'JavaOne', 'Gr8conf' * } * } * * // creates a data structure made of maps (Json object) and lists (Json array) * assert root instanceof Map * * assert builder.toString() == '{"people":{"person":{"firstName":"Guillame","lastName":"Laforge","address":{"city":"Paris","country":"France","zip":12345},"married":true,"conferences":["JavaOne","Gr8conf"]}}}' * </code></pre> * * @author Guillaume Laforge * @author Andrey Bloshetsov * @since 1.8.0 */ public class JsonBuilder extends GroovyObjectSupport implements Writable { private final JsonGenerator generator; private Object content; /** * Instantiates a JSON builder. */ public JsonBuilder() { this.generator = JsonOutput.DEFAULT_GENERATOR; } /** * Instantiates a JSON builder with a configured generator. * * @param generator used to generate the output * @since 2.5 */ public JsonBuilder(JsonGenerator generator) { this.generator = generator; } /** * Instantiates a JSON builder with some existing data structure. * * @param content a pre-existing data structure */ public JsonBuilder(Object content) { this.content = content; this.generator = JsonOutput.DEFAULT_GENERATOR; } /** * Instantiates a JSON builder with some existing data structure * and a configured generator. * * @param content a pre-existing data structure * @param generator used to generate the output * @since 2.5 */ public JsonBuilder(Object content, JsonGenerator generator) { this.content = content; this.generator = generator; } public Object getContent() { return content; } /** * Named arguments can be passed to the JSON builder instance to create a root JSON object * <p> * Example: * <pre><code class="groovyTestCase"> * def json = new groovy.json.JsonBuilder() * json name: "Guillaume", age: 33 * * assert json.toString() == '{"name":"Guillaume","age":33}' * </code></pre> * * @param m a map of key / value pairs * @return a map of key / value pairs */ public Object call(Map m) { content = m; return content; } /** * A list of elements as arguments to the JSON builder creates a root JSON array * <p> * Example: * <pre><code class="groovyTestCase"> * def json = new groovy.json.JsonBuilder() * def result = json([1, 2, 3]) * * assert result instanceof List * assert json.toString() == "[1,2,3]" * </code></pre> * * @param l a list of values * @return a list of values */ public Object call(List l) { content = l; return content; } /** * Varargs elements as arguments to the JSON builder create a root JSON array * <p> * Example: * <pre><code class="groovyTestCase"> * def json = new groovy.json.JsonBuilder() * def result = json 1, 2, 3 * * assert result instanceof List * assert json.toString() == "[1,2,3]" * </code></pre> * * @param args an array of values * @return a list of values */ public Object call(Object... args) { List<Object> listContent = new ArrayList<Object>(); for (Object it : args) { listContent.add(it); } content = listContent; return content; } /** * A collection and closure passed to a JSON builder will create a root JSON array applying * the closure to each object in the collection * <p> * Example: * <pre><code class="groovyTestCase"> * class Author { * String name * } * def authors = [new Author (name: "Guillaume"), new Author (name: "Jochen"), new Author (name: "Paul")] * * def json = new groovy.json.JsonBuilder() * json authors, { Author author -> * name author.name * } * * assert json.toString() == '[{"name":"Guillaume"},{"name":"Jochen"},{"name":"Paul"}]' * </code></pre> * @param coll a collection * @param c a closure used to convert the objects of coll * @return a list of values */ public Object call(Iterable coll, Closure c) { List<Object> listContent = new ArrayList<Object>(); if (coll != null) { for (Object it : coll) { listContent.add(JsonDelegate.curryDelegateAndGetContent(c, it)); } } content = listContent; return content; } /** * Delegates to {@link #call(Iterable, Closure)} */ public Object call(Collection coll, Closure c) { return call((Iterable)coll, c); } /** * A closure passed to a JSON builder will create a root JSON object * <p> * Example: * <pre><code class="groovyTestCase"> * def json = new groovy.json.JsonBuilder() * def result = json { * name "Guillaume" * age 33 * } * * assert result instanceof Map * assert json.toString() == '{"name":"Guillaume","age":33}' * </code></pre> * * @param c a closure whose method call statements represent key / values of a JSON object * @return a map of key / value pairs */ public Object call(Closure c) { content = JsonDelegate.cloneDelegateAndGetContent(c); return content; } /** * A method call on the JSON builder instance will create a root object with only one key * whose name is the name of the method being called. * This method takes as arguments: * <ul> * <li>a closure</li> * <li>a map (ie. named arguments)</li> * <li>a map and a closure</li> * <li>or no argument at all</li> * </ul> * <p> * Example with a classical builder-style: * <pre><code class="groovyTestCase"> * def json = new groovy.json.JsonBuilder() * def result = json.person { * name "Guillaume" * age 33 * } * * assert result instanceof Map * assert json.toString() == '{"person":{"name":"Guillaume","age":33}}' * </code></pre> * * Or alternatively with a method call taking named arguments: * <pre><code class="groovyTestCase"> * def json = new groovy.json.JsonBuilder() * json.person name: "Guillaume", age: 33 * * assert json.toString() == '{"person":{"name":"Guillaume","age":33}}' * </code></pre> * * If you use named arguments and a closure as last argument, * the key/value pairs of the map (as named arguments) * and the key/value pairs represented in the closure * will be merged together — * the closure properties overriding the map key/values * in case the same key is used. * <pre><code class="groovyTestCase"> * def json = new groovy.json.JsonBuilder() * json.person(name: "Guillaume", age: 33) { town "Paris" } * * assert json.toString() == '{"person":{"name":"Guillaume","age":33,"town":"Paris"}}' * </code></pre> * * The empty args call will create a key whose value will be an empty JSON object: * <pre><code class="groovyTestCase"> * def json = new groovy.json.JsonBuilder() * json.person() * * assert json.toString() == '{"person":{}}' * </code></pre> * * @param name the single key * @param args the value associated with the key * @return a map with a single key */ public Object invokeMethod(String name, Object args) { if (args != null && Object[].class.isAssignableFrom(args.getClass())) { Object[] arr = (Object[]) args; if (arr.length == 0) { return setAndGetContent(name, new HashMap<String, Object>()); } else if (arr.length == 1) { if (arr[0] instanceof Closure) { return setAndGetContent(name, JsonDelegate.cloneDelegateAndGetContent((Closure) arr[0])); } else if (arr[0] instanceof Map) { return setAndGetContent(name, arr[0]); } } else if (arr.length == 2) { final Object first = arr[0]; final Object second = arr[1]; if (second instanceof Closure) { final Closure closure = (Closure)second; if (first instanceof Map) { Map subMap = new LinkedHashMap(); subMap.putAll((Map) first); subMap.putAll(JsonDelegate.cloneDelegateAndGetContent(closure)); return setAndGetContent(name, subMap); } else if (first instanceof Iterable) { List<Map<String, Object>> list = collectContentForEachEntry((Iterable) first, closure); return setAndGetContent(name, list); } else if (first != null && first.getClass().isArray()) { final Iterable coll = Arrays.asList((Object[])first); List<Map<String, Object>> list = collectContentForEachEntry(coll, closure); return setAndGetContent(name, list); } } } throw new JsonException("Expected no arguments, a single map, a single closure, or a map and closure as arguments."); } else { return setAndGetContent(name, new HashMap<String, Object>()); } } private static List<Map<String, Object>> collectContentForEachEntry(Iterable coll, Closure closure) { List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); for (Object it : coll) { list.add(JsonDelegate.curryDelegateAndGetContent(closure, it)); } return list; } private Object setAndGetContent(String name, Object value) { Map<String, Object> contentMap = new LinkedHashMap<String, Object>(); contentMap.put(name, value); content = contentMap; return content; } /** * Serializes the internal data structure built with the builder to a conformant JSON payload string * <p> * Example: * <pre><code class="groovyTestCase"> * def json = new groovy.json.JsonBuilder() * json { temperature 37 } * * assert json.toString() == '{"temperature":37}' * </code></pre> * * @return a JSON output */ public String toString() { return generator.toJson(content); } /** * Pretty-prints and formats the JSON payload. * <p> * This method calls the JsonLexer to parser the output of the builder, * so this may not be an optimal method to call, * and should be used mainly for debugging purpose * for a human-readable output of the JSON content. * * @return a pretty printed JSON output */ public String toPrettyString() { return JsonOutput.prettyPrint(toString()); } /** * The JSON builder implements the <code>Writable</code> interface, * so that you can have the builder serialize itself the JSON payload to a writer. * <p> * Example: * <pre><code class="groovyTestCase"> * def json = new groovy.json.JsonBuilder() * json { temperature 37 } * * def out = new StringWriter() * out << json * * assert out.toString() == '{"temperature":37}' * </code></pre> * * @param out a writer on which to serialize the JSON payload * @return the writer */ public Writer writeTo(Writer out) throws IOException { return out.append(toString()); } }