Grails JSONBuilder - PullRequest
       11

Grails JSONBuilder

11 голосов
/ 04 апреля 2011

Если у меня есть простой объект, такой как

class Person {
  String name
  Integer age
}

, я могу легко отобразить его пользовательские свойства как JSON, используя 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()

Это прекрасно работает, когда свойствапростой, то есть числа или строки.Однако для более сложного объекта, такого как

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

Обновление

Я не думаю, что могу использовать JSON-конвертер Grails для этогопотому что реальная структура JSON, которую я возвращаю, выглядит примерно так:

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

Обратите внимание, что:

  • JSON, сгенерированный для ComplexPerson, является элементом более крупного объекта JSON
  • Я хочу исключить некоторые свойства, такие как metaClass и class, из преобразования JSON

Если возможно получить выходные данные преобразователя JSON в виде объекта, я мог быитерируйте и удалите свойства metaClass и class, затем добавьте его во внешний объект JSON.

Однако, насколько я могу судить, конвертер JSON, похоже, предлагает только "все илиничего "подход и возвратвыводится в виде строки

Ответы [ 2 ]

13 голосов
/ 05 апреля 2011

Я наконец-то понял, как это сделать, используя 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 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()

Должен быть сгенерирован следующий вывод, в котором в качестве значения свойства object хранится сериализованный экземпляр Complex:

{"object": {
   "nest2": {
      "p2": "val2",
      "p1": "val1"
   },
   "nest1": {
      "p2": "val2",
      "p1": "val1"
   },
   "name": null
}}
8 голосов
/ 04 апреля 2011

Чтобы преобразователь мог преобразовать всю структуру объекта, вам нужно установить свойство в конфигурации, чтобы указать, что в противном случае он будет просто включать идентификатор дочернего объекта, поэтому вам нужно добавить это:

grails.converters.json.default.deep = true

Для получения дополнительной информации перейдите по ссылке Справочник по преобразователям Grails .

Однако, как вы упомянули в комментарии выше, это все или ничего, поэтому вы можете создать своего собственного маршаллера для своего класса. Я должен был сделать это раньше, потому что мне нужно было включить некоторые очень специфические свойства, поэтому я создал класс, расширяющий 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
}

Как вы можете видеть, этот маршаллер работает для определенного класса, поэтому, если вы хотите сделать более универсальный, вы можете создать супер класс и сделать так, чтобы ваши классы наследовали от него, так что в support method вы говорите, что этот маршаллер поддерживает все классы, которые являются экземплярами этого суперкласса.

Я предлагаю пересмотреть код grails для конвертеров, который даст вам представление о том, как они работают внутри, а затем о том, как вы можете расширить его, чтобы он работал так, как вам нужно.

Этот другой пост в Nabble тоже может помочь.

Кроме того, если вам нужно сделать это и для XML, вы просто расширяете класс org.codehaus.groovy.grails.web.converters.marshaller.xml.DomainClassMarshaller и выполняете тот же процесс, чтобы зарегистрировать его и т. Д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...