Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
895 views
in Technique[技术] by (71.8m points)

json - Grails JSONBuilder

If I have a simple object such as

class Person {
  String name
  Integer age
}

I can easily render it's user-defined properties as JSON using the JSONBuilder

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

This works fine when the properties are simple, i.e. numbers or strings. However for a more complex object such as

class ComplexPerson {
  Name name
  Integer age
  Address address
}

class Name {
  String first
  String second
}

class Address {
  Integer houseNumber
  String streetName
  String country

}

Is there a way that I can walk the entire object graph, adding each user-defined property at the appropriate nesting level to the JSONBuilder?

In other words, for an instance of ComplexPerson I would like the output to be

{
  name: {
    first: 'john',
    second: 'doe'
  },
  age: 20,
  address: {
    houseNumber: 123,
    streetName: 'Evergreen Terrace',
    country: 'Iraq'
  }
}

Update

I don't think I can use the Grails JSON converter to do this because the actual JSON structure I'm returning looks something like

{ status: false,
  message: "some message",
  object: // JSON for person goes here 
}

Notice that:

  • The JSON generated for the ComplexPerson is an element of a larger JSON object
  • I want to exclude certain properties such as metaClass and class from the JSON conversion

If it's possible to get the output of the JSON converter as an object, I could iterate over that and remove the metaClass and class properties, then add it to the outer JSON object.

However, as far as I can tell, the JSON converter only seems to offer an "all or nothing" approach and returns it output as a String

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I finally figured out how to do this using a JSONBuilder, here's the code

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
    }
}

You can test this by pasting the code above along with the following into the grails console

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

It should generate the following output which stores the serialized instance of Complex as the value of the object property:

{"object": {
   "nest2": {
      "p2": "val2",
      "p1": "val1"
   },
   "nest1": {
      "p2": "val2",
      "p1": "val1"
   },
   "name": null
}}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...