一尘不染

Grails JSONBuilder

json

如果我有一个简单的对象,例如

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 
}

注意:

  • 为生成的JSON ComplexPerson是较大JSON对象的元素
  • 我想从JSON转换中排除某些属性,例如metaClassandclass

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

但是,据我所知,JSON转换器似乎仅提供“全有或全无”方法,并将其输出返回为String


阅读 267

收藏
2020-07-27

共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()

它应生成以下输出,该输出将的序列化实例存储Complexobject属性的值:

{"object": {
   "nest2": {
      "p2": "val2",
      "p1": "val1"
   },
   "nest1": {
      "p2": "val2",
      "p1": "val1"
   },
   "name": null
}}
2020-07-27