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 转换中排除某些属性,例如 metaClassclass

如果可以将 JSON 转换器的输出作为对象获取,我可以对其进行迭代并删除 metaClassclass 属性,然后将其添加到外部 JSON 对象。

然而,据我所知,JSON 转换器似乎只提供“全有或全无”的方法,并将其作为字符串返回输出

【问题讨论】:

person as grails.converters.JSON 不会为你走树吗?我一直认为它确实(但从未尝试过) 确实如此,但我想在输出中添加一些额外的属性,同时排除classmetaClass 和其他一些属性。转换器似乎提供了一种“全有或全无”的方法,这就是为什么我想用构建器来代替。 【参考方案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 验证?如何?

Grails:企业级 Grails [关闭]

Grails - grails.converters.JSON - 删除类名

Grails - 在刷新 grails 错误之前保存瞬态实例?

Grails 3 - 资源插件