如果我有一个简单的对象,例如
class Person {
String name
Integer age
}
我可以使用JSONBuilder
轻松地将其用户定义的属性呈现为JSONdef 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对象的元素metaClass
和class
如果可以将JSON转换器的输出作为对象,我可以迭代它并删除metaClass
和class
属性,然后将其添加到外部JSON对象。
但是,据我所知,JSON转换器似乎只提供“全有或全无”方法并将其作为字符串返回
答案 0 :(得分:13)
我终于想出了如何使用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
}}
答案 1 :(得分:8)
为了让转换器转换整个对象结构,你需要在config中设置一个属性来指示,否则它只会包含子对象的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
}
正如您所看到的,这个marshaller适用于特定的类,因此如果您想要更通用,那么您可以创建一个超类,并使您的类继承,因此在 support 中你所做的就是说这个marshaller支持所有超类的实例类。
我的建议是查看转换器的grails代码,这将使您了解它们如何在内部工作,然后如何扩展它以使其按您需要的方式工作。
其他post in Nabble也可能有所帮助。
此外,如果您还需要为XML做,那么您只需扩展类org.codehaus.groovy.grails.web.converters.marshaller.xml.DomainClassMarshaller并按照相同的过程进行注册,等等。 / p>