Grails JSONBuilder
Posted
技术标签:
【中文标题】Grails JSONBuilder【英文标题】: 【发布时间】:2011-07-29 03:32:45 【问题描述】:如果我有一个简单的对象,例如
class Person
String name
Integer age
我可以使用 JSONBuilder 轻松地将其用户定义的属性呈现为 JSON
def person = new Person(name: 'bob', age: 22)
def builder = new JSONBuilder.build
person.properties.each propName, propValue ->
if (!['class', 'metaClass'].contains(propName))
// It seems "propName = propValue" doesn't work when propName is dynamic so we need to
// set the property on the builder using this syntax instead
setProperty(propName, propValue)
def json = builder.toString()
当属性很简单(即数字或字符串)时,这可以正常工作。但是对于更复杂的对象,例如
class ComplexPerson
Name name
Integer age
Address address
class Name
String first
String second
class Address
Integer houseNumber
String streetName
String country
有没有一种方法可以遍历整个对象图,将每个用户定义的属性在适当的嵌套级别添加到 JSONBuilder?
换句话说,对于ComplexPerson
的实例,我希望输出为
name:
first: 'john',
second: 'doe'
,
age: 20,
address:
houseNumber: 123,
streetName: 'Evergreen Terrace',
country: 'Iraq'
更新
我认为我不能使用 Grails JSON 转换器来执行此操作,因为我返回的实际 JSON 结构看起来类似于
status: false,
message: "some message",
object: // JSON for person goes here
注意:
为ComplexPerson
生成的 JSON 是更大 JSON 对象的一个元素
我想从 JSON 转换中排除某些属性,例如 metaClass
和 class
如果可以将 JSON 转换器的输出作为对象获取,我可以对其进行迭代并删除 metaClass
和 class
属性,然后将其添加到外部 JSON 对象。
然而,据我所知,JSON 转换器似乎只提供“全有或全无”的方法,并将其作为字符串返回输出
【问题讨论】:
person as grails.converters.JSON
不会为你走树吗?我一直认为它确实(但从未尝试过)
确实如此,但我想在输出中添加一些额外的属性,同时排除class
、metaClass
和其他一些属性。转换器似乎提供了一种“全有或全无”的方法,这就是为什么我想用构建器来代替。
【参考方案1】:
我终于想出了如何使用JSONBuilder
来做到这一点,这是代码
import grails.web.*
class JSONSerializer
def target
String getJSON()
Closure jsonFormat =
object =
// Set the delegate of buildJSON to ensure that missing methods called thereby are routed to the JSONBuilder
buildJSON.delegate = delegate
buildJSON(target)
def json = new JSONBuilder().build(jsonFormat)
return json.toString(true)
private buildJSON = obj ->
obj.properties.each propName, propValue ->
if (!['class', 'metaClass'].contains(propName))
if (isSimple(propValue))
// It seems "propName = propValue" doesn't work when propName is dynamic so we need to
// set the property on the builder using this syntax instead
setProperty(propName, propValue)
else
// create a nested JSON object and recursively call this function to serialize it
Closure nestedObject =
buildJSON(propValue)
setProperty(propName, nestedObject)
/**
* A simple object is one that can be set directly as the value of a JSON property, examples include strings,
* numbers, booleans, etc.
*
* @param propValue
* @return
*/
private boolean isSimple(propValue)
// This is a bit simplistic as an object might very well be Serializable but have properties that we want
// to render in JSON as a nested object. If we run into this issue, replace the test below with an test
// for whether propValue is an instanceof Number, String, Boolean, Char, etc.
propValue instanceof Serializable || propValue == null
您可以通过将上面的代码以及以下代码粘贴到 grails 控制台
来测试这一点// Define a class we'll use to test the builder
class Complex
String name
def nest2 = new Expando(p1: 'val1', p2: 'val2')
def nest1 = new Expando(p1: 'val1', p2: 'val2')
// test the class
new JSONSerializer(target: new Complex()).getJSON()
它应该生成以下输出,它将Complex
的序列化实例存储为object
属性的值:
"object":
"nest2":
"p2": "val2",
"p1": "val1"
,
"nest1":
"p2": "val2",
"p1": "val1"
,
"name": null
【讨论】:
【参考方案2】:为了让转换器转换整个对象结构,你需要在配置中设置一个属性来表示,否则它只会包含子对象的ID,所以你需要添加这个:
grails.converters.json.default.deep = true
欲了解更多信息,请转至Grails Converters Reference。
但是,就像您在上面的评论中提到的那样,它是全有或全无,所以您可以做的是为您的班级创建自己的编组器。我之前必须这样做,因为我需要包含一些非常具体的属性,所以我所做的是我创建了一个扩展 org.codehaus.groovy.grails.web.converters.marshaller.json.DomainClassMarshaller 的类。比如:
class MyDomainClassJSONMarshaller extends DomainClassMarshaller
public MyDomainClassJSONMarshaller()
super(false)
@Override
public boolean supports(Object o)
return (ConverterUtil.isDomainClass(o.getClass()) &&
(o instanceof MyDomain))
@Override
public void marshalObject(Object value, JSON json) throws ConverterException
JSONWriter writer = json.getWriter();
Class clazz = value.getClass();
GrailsDomainClass domainClass = ConverterUtil.getDomainClass(clazz.getName());
BeanWrapper beanWrapper = new BeanWrapperImpl(value);
writer.object();
writer.key("class").value(domainClass.getClazz().getName());
GrailsDomainClassProperty id = domainClass.getIdentifier();
Object idValue = extractValue(value, id);
json.property("id", idValue);
GrailsDomainClassProperty[] properties = domainClass.getPersistentProperties();
for (GrailsDomainClassProperty property: properties)
if (!DomainClassHelper.isTransient(transientProperties, property))
if (!property.isAssociation())
writer.key(property.getName());
// Write non-relation property
Object val = beanWrapper.getPropertyValue(property.getName());
json.convertAnother(val);
else
Object referenceObject = beanWrapper.getPropertyValue(property.getName());
if (referenceObject == null)
writer.key(property.getName());
writer.value(null);
else
if (referenceObject instanceof AbstractPersistentCollection)
if (isRenderDomainClassRelations(value))
writer.key(property.getName());
// Force initialisation and get a non-persistent Collection Type
AbstractPersistentCollection acol = (AbstractPersistentCollection) referenceObject;
acol.forceInitialization();
if (referenceObject instanceof SortedMap)
referenceObject = new TreeMap((SortedMap) referenceObject);
else if (referenceObject instanceof SortedSet)
referenceObject = new TreeSet((SortedSet) referenceObject);
else if (referenceObject instanceof Set)
referenceObject = new HashSet((Set) referenceObject);
else if (referenceObject instanceof Map)
referenceObject = new HashMap((Map) referenceObject);
else
referenceObject = new ArrayList((Collection) referenceObject);
json.convertAnother(referenceObject);
else
writer.key(property.getName());
if (!Hibernate.isInitialized(referenceObject))
Hibernate.initialize(referenceObject);
json.convertAnother(referenceObject);
writer.endObject();
...
上面的代码与 DomainClassMarshaller 的代码几乎相同,想法是您添加或删除您需要的内容。
然后为了让 Grails 使用这个新转换器,您需要在 resources.groovy 文件中注册它,如下所示:
// Here we are regitering our own domain class JSON Marshaller for MyDomain class
myDomainClassJSONObjectMarshallerRegisterer(ObjectMarshallerRegisterer)
converterClass = grails.converters.JSON.class
marshaller = MyDomainClassJSONMarshaller myDomainClassJSONObjectMarshaller ->
// nothing to configure, just need the instance
priority = 10
正如你所看到的,这个编组器适用于特定的类,所以如果你想更通用,你可以做的是创建一个超类并让你的类从它继承,所以在 支持你所做的就是说这个编组器支持作为该超类实例的所有类。
我的建议是查看转换器的 grails 代码,这将使您了解它们在内部是如何工作的,然后您可以如何扩展它以使其按您需要的方式工作。
另一个post in Nabble 也可能有帮助。
另外,如果你也需要为 XML 做这件事,那么你只需扩展类 org.codehaus.groovy.grails.web.converters.marshaller.xml.DomainClassMarshaller 并按照相同的过程来注册它,等等。
【讨论】:
非常感谢,这看起来很有帮助 希望它有效,我认为您可以使用它创建一个非常自定义的转换器,因此它可以满足您的需求:-)以上是关于Grails JSONBuilder的主要内容,如果未能解决你的问题,请参考以下文章
是否可以在 Grails 之外使用 Grails 验证?如何?
Grails - grails.converters.JSON - 删除类名