我从未尝试过使用JsonGroovyBuilder,因此无法真正帮助您。 Grails 1.1 提供的 JSON Builder(在 Grails 1.2 中被更好的版本取代)让我同样感到沮丧。我通过编写自己的 Groovy JSON 构建器克服了这种挫败感,欢迎您使用。我已经粘贴了下面的源代码。
import org.json.JSONStringer
/**
* An alternative JSON builder, because <code>grails.util.JSonBuilder</code> sucks!
* The reasons why it sucks are described here: http://www.anyware.co.uk/2005/2008/06/19/a-grails-json-builder-that-doesnt-suck/
* Although this page provides an alternative JSON builder, the author didn't provide any usage examples, and I couldn't
* figure out how to use it, so I wrote my own instead.
*
* The underlying JSON functionality is provided by <code>json.org.JSONStringer</code>. An example usage is:
*
* <code>
* builder.array(['feck', 'arse', 'girls']) {
* foo(bar:'1', baz: '2') {
* hobbies(sport: 'snooker', drink: 'guinness')
* emptyObj([:])
* emptyArray([])
* }
* }
* builder.json
* </code>
*
* This will print:
* <code>
* ["feck","arse","girls", {"bar":"1", "baz":"2", "hobbies": {"sport":"snooker", "drink":"guinness"}, "emptyObj": {},"emptyArray":[]}]
* </code>
*
* Verifiable usage examples are provided by the unit tests. A few points worth noting (the term 'element' is used below
* to mean 'either a JSON object or JSON array'):
*
* <ul>
* <li>The nesting of elements is indicated by the nesting of closures in the usual Groovy builder fashion</li>
* <li>The name of the method is used as the name of the key when nesting an element within an object</li>
* <li>The name of the method is irrelevant when nesting an element within an array, but it is recommended
* to use either the method name 'object' or 'array' for the sake of code readability</li>
* <li>The decision to create an array or object is determined by the type of the method parameter. A map will cause
* an object to be created, any other type will cause an array to be created</li>
* <li>To create an empty array or an array whose contents are determined only by nested closures, either call
* <code>builder.array()</code> or <code>builder.keyName([])</code>. The latter should be used when nesting the empty
* array within an object and control over the key name is required.</li>
* <li>To create an empty object or an object whose contents are determined only by nested closures, either call
* <code>builder.object()</code> or <code>builder.keyName([:])</code>. The latter should be used when nesting the empty
* object within another object and control over the key name is required.</li>
* </ul>
*/
class SimpleJSONBuilder extends BuilderSupport {
private jsonText = new JSONStringer()
/**
* Returns the JSON text created by this builder
*/
String getJson() {
jsonText.toString()
}
String toString() {
getJson()
}
protected void setParent(Object parent, Object child) {
// Method is abstract in parent class, but an empty implementation is all we need
}
/**
* Creates an array or object which is either empty, or whose contents are determined exclusively by nested closures.
*/
protected Object createNode(Object name) {
if (current == ElementType.OBJECT) {
throw new IllegalStateException("""Error processing method $name() Empty argument methods should not be invoked
when nesting an element within an object because the key name cannot be determined. Replace this call with either
$name([]) or $name([:])""")
}
if (name == 'array') {
jsonText.array()
return ElementType.ARRAY
} else if (name == 'object') {
jsonText.object()
return ElementType.OBJECT
} else {
throw new IllegalArgumentException("""When calling a method with no arguments, the method must be named either
'$array' or '$object' to indicate which you wish to create""")
}
}
protected Object createNode(Object name, Map attributes, Object value) {
throw new UnsupportedOperationException("Error invoking method $name. Method calls must supply either a single object (to create an array) or a Map (to create an object)")
}
/**
* Ensures that an array/object is correctly nested within an object
* @name Name of the key to use for the nested element
* @return The type of element
*/
private void nestElement(name) {
if (current == ElementType.OBJECT) {
jsonText.key(name)
}
}
/**
* Creates an array
* @name Name of the method. This will be used as the key if the array is nested within an object
* @value The contents of the array. This should be either a single value or a collection or array
* @return The type of element
*/
protected Object createNode(Object name, Object value) {
nestElement(name)
jsonText.array()
if (value instanceof Collection || value instanceof Object[]) {
value.each {jsonText.value(it)}
} else {
jsonText.value(value)
}
return ElementType.ARRAY
}
/**
* Creates an object
* @name Name of the method. This will be used as the key if the object is nested within an object
* @value The name-value pairs contained by this object
* @return The type of element
*/
protected Object createNode(Object name, Map attributes) {
nestElement(name)
jsonText.object()
attributes.each {key, value ->
jsonText.key(key).value(value)
}
return ElementType.OBJECT
}
protected void nodeCompleted(Object parent, Object node) {
node == ElementType.OBJECT ? jsonText.endObject() : jsonText.endArray()
}
}
private enum ElementType {
ARRAY, OBJECT
}
上面的源代码定义了类SimpleJSONBuilder和枚举SimpleJSONBuilder,但是你可以将这两个存储在一个文件SimpleJSONBuilder.groovy中。
此构建器所需的唯一库是json.org 提供的Java JSON 库。
如果上面代码中的 cmets 没有很好地解释如何使用它,这里有一些测试用例:
public class SimpleJSONBuilderTests extends GroovyTestCase {
void testRootArrayElement() {
def builder = new SimpleJSONBuilder()
builder.array(['feck', 'arse', 'girls']) {
foo(bar: '1', baz: '2') {
hobbies(sport: 'snooker', drink: 'guinness')
emptyObj([:])
emptyArray([])
}
}
assertEquals builder.json, '["feck","arse","girls",{"bar":"1","baz":"2","hobbies":{"sport":"snooker","drink":"guinness"},"emptyObj":{},"emptyArray":[]}]'
}
void testRootObjElement() {
def builder = new SimpleJSONBuilder()
builder.object(feck:'arse') {
foo(bar: '1', baz: '2') {
hobbies(sport: 'snooker', drink: 'guinness')
emptyObj([:])
emptyArray([])
}
}
assertEquals builder.json, '{"feck":"arse","foo":{"bar":"1","baz":"2","hobbies":{"sport":"snooker","drink":"guinness"},"emptyObj":{},"emptyArray":[]}}'
}
/**
* Test that both mechanisms for creating empty arrays are equivalent
*/
void testEmptyArrays() {
def builder = new SimpleJSONBuilder()
builder.array([])
def builder2 = new SimpleJSONBuilder()
builder2.array()
assertEquals builder.json, builder2.json
assertEquals builder.json, "[]"
}
/**
* Test that both mechanisms for creating empty objects are equivalent
*/
void testEmptyObjects() {
def builder = new SimpleJSONBuilder()
builder.object([:])
def builder2 = new SimpleJSONBuilder()
builder2.object()
assertEquals builder.json, builder2.json
assertEquals builder.json, "{}"
}
}