Grails JSONBuilder

前端 未结 2 596
说谎
说谎 2020-12-01 08:22

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 us

相关标签:
2条回答
  • 2020-12-01 09:03

    In order for the converter to convert the whole object structure you need to set a property in the config to indicate that, otherwise it will just include the ID of the child object, so you need to add this:

    grails.converters.json.default.deep = true
    

    For more information go Grails Converters Reference.

    However, like you mentioned it in the comment above it is all or nothing, so what you can do is create your own marshaller for your class. I had to do this before because I needed to include some very specific properties, so what I did was that I created a class that extends org.codehaus.groovy.grails.web.converters.marshaller.json.DomainClassMarshaller. Something like:

    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();
      }
      ...
    }
    

    That code above is pretty much the same code as it is DomainClassMarshaller, the idea would be that you add or remove what you need.

    Then in order for Grails to use this new converter what you need is to register it in the resources.groovy file, like this:

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

    As you can see this marshaller works for a specific class, so if you want to make more generic what you can do is create a super class and make your classes inherit from that so in the support method what you do is say this marshaller support all the classes that are instances of that super class.

    My suggestion is to review the grails code for the converters, that will give you an idea of how they work internally and then how you can extend it so it works the way you need.

    This other post in Nabble might be of help too.

    Also, if you need to do it for XML as well then you just extend the class org.codehaus.groovy.grails.web.converters.marshaller.xml.DomainClassMarshaller and follow the same process to register it, etc.

    0 讨论(0)
  • 2020-12-01 09:21

    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
    }}
    
    0 讨论(0)
提交回复
热议问题