Coder Social home page Coder Social logo

grails-gson's Introduction

Grails Gson plugin

Build Status

This plugin provides alternate JSON (de)serialization for Grails using Google's Gson library.

Rationale

Grails' JSON deserialization has some limitations. Specifically it doesn't work with nested object graphs. This means you can't bind a JSON data structure to a GORM domain class and have it populate associations, embedded properties, etc.

There is a JIRA open for this issue but since it's easy to provide an alternative with Gson I thought a plugin was worthwhile.

Installation

Add compile 'org.grails.plugins:gson:1.1.4' to grails-app/conf/BuildConfig.groovy.

Usage

Using Grails converters

The plugin provides a Grails converter implementation so that you can replace usage of the existing grails.converters.JSON class with grails.plugin.gson.converters.GSON. For example:

import grails.plugin.gson.converters.GSON

class PersonController {
	def list() {
		render Person.list(params) as GSON
	}

	def save() {
		def personInstance = new Person(request.GSON)
		// ... etc.
	}

	def update() {
		def personInstance = Person.get(params.id)
		personInstance.properties = request.GSON
		// ... etc.
	}
}

Using Gson directly

The plugin provides a GsonBuilder factory bean that you can inject into your components. This is pre-configured to register type handlers for domain classes so you don't need to worry about doing so unless you need to override specific behaviour.

class PersonController {
	def gsonBuilder

	def list() {
		def gson = gsonBuilder.create()
		def personInstances = Person.list(params)
		render contentType: 'application/json', text: gson.toJson(personInstances)
	}

	def save() {
		def gson = gsonBuilder.create()
		def personInstance = gson.fromJson(request.reader, Person)
		if (personInstance.save()) {
			// ... etc.
	}

	def update() {
		def gson = gsonBuilder.create()
		// because the incoming JSON contains an id this will read the Person
		// from the database and update it!
		def personInstance = gson.fromJson(request.reader, Person)
	}
}

Serialization

By default the plugin will automatically serialize any Hibernate proxies it encounters when serializing an object graph to JSON, resolving any uninitialized proxies along the way. This means by default you get a full, deep object graph at the potential cost of additional SQL queries. There are two config flags to control this behavior in your Config.groovy. If you set grails.converters.gson.resolveProxies to false then only initialized proxies are serialized โ€“ therefore no additional queries are performed. If you set grails.converters.gson.serializeProxies to false then no proxies are serialized at all meaning your JSON will only contain a shallow object graph.

If an object graph contains bi-directional relationships they will only be traversed once (but in either direction).

For example if you have the following domain classes:

class Artist {
	String name
	static hasMany = [albums: Album]
}

class Album {
	String title
	static belongsTo = [artist: Artist]
}

Instances of Album will get serialized to JSON as:

{
	"id": 2,
	"title": "The Rise and Fall of Ziggy Stardust and the Spiders From Mars",
	"artist": {
		"id": 1,
		"name": "David Bowie"
	}
}

And instances of Artist will get serialized to JSON as:

{
	"id": 1,
	"name": "David Bowie",
	"albums": [
		{ "id": 1, "title": "Hunky Dory" },
		{ "id": 2, "title": "The Rise and Fall of Ziggy Stardust and the Spiders From Mars" },
		{ "id": 3, "title": "Low" }
	]
}

Deserialization

The plugin registers a JsonDeserializer that handles conversion of JSON to Grails domain objects. It will handle deserialization at any level of a JSON object graph so embedded objects, relationships and persistent collections can all be modified when binding to the top level domain object instance.

The deserializer is pre-configured to handle:

  • domain classes
  • domain associations
  • Set, List and Map associations
  • embedded properties
  • collections of basic types
  • arbitrary depth object graphs

If a JSON object contains an id property then it will use GORM to retrieve an existing instance, otherwise it creates a new one.

The deserializer respects the bindable constraint so any properties that are blacklisted from binding are ignored. Any JSON properties that do not correspond to persistent properties on the domain class are ignored. Any other properties of the JSON object are bound to the domain instance.

Deserialization examples

Let's say you have a domain classes Child and Pet like this:

class Child {
	String name
	int age
	static hasMany = [pets: Pet]
}

class Pet {
	String name
	String species
	static belongsTo = [child: Child]
}

This can be deserialized in a number of ways.

To create a new Child instance with associated Pet instances

{
	"name": "Alex",
	"age": 3,
	"pets": [
		{"name": "Goldie", "species": "Goldfish"},
		{"name": "Dottie", "species": "Goldfish"}
	]
}

To bind new Pet instances to an existing Child

{
	"id": 1,
	"pets": [
		{"name": "Goldie", "species": "Goldfish"},
		{"name": "Dottie", "species": "Goldfish"}
	]
}

To bind existing Pet instances to a new Child

{
	"name": "Alex",
	"age": 3,
	"pets": [
		{"id": 1},
		{"id": 2}
	]
}

To update the name of existing Pet instances without changing their species

{
	"id": 1,
	"pets": [
		{"id": 1, "name": "Goldie"},
		{"id": 2, "name": "Dottie"}
	]
}

Registering additional type adapters

The gsonBuilder factory bean provided by the plugin will automatically register any Spring beans that implement the TypeAdapterFactory interface.

Example

To register support for serializing and deserializing org.joda.time.LocalDate properties you would define a TypeAdapter implementation:

class LocalDateAdapter extends TypeAdapter<LocalDate> {

	private final formatter = ISODateTimeFormat.date()

	void write(JsonWriter jsonWriter, LocalDateTime t) {
		jsonWriter.value(t.toString(formatter))
	}

	LocalDateTime read(JsonReader jsonReader) {
		formatter.parseLocalDate(jsonReader.nextString())
	}
}

Then create a TypeAdapterFactory:

class LocalDateAdapterFactory implements TypeAdapterFactory {
	TypeAdapter create(Gson gson, TypeToken type) {
		type.rawType == LocalDate ? new LocalDateAdapter() : null
	}
}

Finally register the TypeAdapterFactory in grails-app/conf/spring/resources.groovy:

beans {
	localDateAdapterFactory(LocalDateAdapterFactory)
}

The plugin will then automatically use it.

See the Gson documentation on custom serialization and deserialization for more information on how to write TypeAdapter implementations.

Unit test support

The plugin provides a test mixin. Simply add @TestMixin(GsonUnitTestMixin) to test or spec classes. The mixin registers beans in the mock application context that are required for the GSON converter class to work properly. It also ensures that binding and rendering works with @Mock domain classes just as it does in a real running application.

In addition the mixin adds:

  • a GSON property on HttpServletResponse for convenience in making assertions in controller tests.
  • a writable GSON property on HttpServletResponse that accepts either a JsonElement or a JSON string.

Scaffolding RESTful controllers

The GSON plugin includes a scaffolding template for RESTful controllers designed to work with Grails' resource style URL mappings. To install the template run:

grails install-gson-templates

This will overwrite any existing file in src/templates/scaffoldng/Controller.groovy. You can then generate RESTful controllers that use GSON using the normal dynamic or static scaffolding capabilities.

Gotchas

When trying to bind an entire object graph you need to be mindful of the way GORM cascades persistence changes.

Cascading updates

Even though you can bind nested domain relationships there need to be cascade rules in place so that they will save.

In the examples above the Pet domain class must declare that it belongsTo Child (or Child must declare that updates cascade to pets). Otherwise the data will bind but when you save the Child instance the changes to any nested Pet instances will not be persisted.

Cascading saves

Likewise if you are trying to create an entire object graph at once the correct cascade rules need to be present.

If Pet declares belongsTo = [child: Child] everything should work as Grails will apply cascade all by default. However if Pet declares belongsTo = Child then Child needs to override the default cascade save-update so that new Pet instances are created properly.

See the Grails documentation on the cascade mapping for more information.

Circular references

Gson does not support serializing object graphs with circular references and a StackOverflowException will be thrown if you try. The plugin protects against circular references caused by bi-directional relationships in GORM domain classes but any other circular reference is likely to cause a problem when serialized. If your domain model contains such relationships you will need to register additional TypeAdapter implementations for the classes involved.

Parameter parsing

In general it is possible to use the Gson plugn alongside Grails' built in JSON support. The only thing the plugin overrides in the parsing of a JSON request body into a parameter map.

This is only done when you set parseRequest: true in URLMappings or use a resource style mapping. See the Grails documentation on REST services for more information.

The plugin's parsing is compatible with that done by the default JSON handler so you should see no difference in the result.

Configuration

The plugin supports a few configurable options. Where equivalent configuration applies to the standard Grails JSON converter then the same configuration can be used for the GSON converter.

  • grails.converters.gson.serializeProxies if set to true then any Hibernate proxies are traversed when serializing entities to JSON. Defaults to true. If set to false any n-to-one proxies are serialized as just their identifier and any n-to-many proxies are omitted altogether.

  • grails.converters.gson.resolveProxies if set to true then any Hibernate proxies are initialized when serializing entities to JSON. Defaults to true. If set to false only proxies that are already initialized get serialized to JSON. This flag has no effect if grails.converters.gson.serializeProxies is set to false as proxies will not be traversed anyway.

  • grails.converters.gson.pretty.print if set to true then serialization will output pretty-printed JSON. Defaults to grails.converters.default.pretty.print or false. See GsonBuilder.setPrettyPrinting.

  • grails.converters.gson.domain.include.class if set to true then serialization will include domain class names. Defaults to grails.converters.domain.include.class or false.

  • grails.converters.gson.domain.include.version if set to true then serialization will include entity version. Defaults to grails.converters.domain.include.version or false.

  • grails.converters.gson.serializeNulls if set to true then null properties are included in serialized JSON, otherwise they are omitted. Defaults to false. See GsonBuilder.serializeNulls.

  • grails.converters.gson.complexMapKeySerialization if set to true then object map keys are serialized as JSON objects, otherwise their toString method is used. Defaults to false. See GsonBuilder.enableComplexMapKeySerialization.

  • grails.converters.gson.escapeHtmlChars if set to true then HTML characters are escaped in serialized output. Defaults to true. See GsonBuilder.disableHtmlEscaping.

  • grails.converters.gson.generateNonExecutableJson if set to true then serialized output is prepended with an escape string to prevent execution as JavaScript. Defaults to false. See GsonBuilder.generateNonExecutableJson.

  • grails.converters.gson.serializeSpecialFloatingPointValues if set to true then serialization will not throw an exception if it encounters a special long value such as NaN. Defaults to false. See GsonBuilder.serializeSpecialFloatingPointValues.

  • grails.converters.gson.longSerializationPolicy specifies how long values are serialized. Defaults to LongSerializationPolicy.DEFAULT. See GsonBuilder.setLongSerializationPolicy.

  • grails.converters.gson.fieldNamingPolicy specifies how field names are serialized. Defaults to FieldNamingPolicy.IDENTITY. See GsonBuilder.setFieldNamingStrategy.

  • grails.converters.gson.datePattern specifies the pattern used to format java.util.Date objects in serialized output. If this is set then dateStyle and timeStyle are ignored. See GsonBuilder.setDateFormat(String).

  • grails.converters.gson.dateStyle and grails.converters.gson.timeStyle specify the style used to format java.util.Date objects in serialized output. See GsonBuilder.setDateFormat(int, int). The values should be one of the int constants - SHORT, MEDIUM, LONG or FULL - from java.text.DateFormat. Note that Gson does not have a way to specify a locale for the format so Locale.US is always used. For more control over the format use grails.converters.gson.datePattern or register a custom TypeAdapterFactory.

Version history

  • Fixes a problem in unit tests with request.GSON = x where x is anything other than a String.
  • Fixes a bug where the plugin breaks domainClass.properties = x where x is anything other than a JsonObject.
  • Adds GsonUnitTestMixin for unit test support.
  • Fixes a compilation problem with scaffolded controllers that use the RESTful controller template
  • Introduces various configuration options
  • Adds RESTful controller template

Bugfix release.

  • Fixes deserialization of bi-directional relationships so tbat the domain instances can be save successfully.
  • Ignores unknown properties in JSON rather than throwing an exception (contributed by @gavinhogan).

Initial release.

grails-gson's People

Contributors

beckje01 avatar marcioj avatar robfletcher avatar slestang avatar tomdcc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

grails-gson's Issues

unable to resolve class HttpConstants?

Steps to repro:

  • install-gson-templates
  • generate-controller Artist
  • compile --> compilation error unable to resolve class grails.plugin.gson.http.HttpConstants

Custom Serializer Not Used Inside Map

package com.app.security

class AuthUser {
String username
String password
}

package com.app.utils.gson

import com.app.security.AuthUser
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer

import java.lang.reflect.Type

class AuthUserSerializer implements JsonSerializer {

public AuthUserSerializer() {
super();
}

JsonElement serialize(AuthUser value, Type type, JsonSerializationContext context) {
final JsonObject jsonObj = new JsonObject();

['id', 'username'].each {
  jsonObj.add(it, context.serialize(value[it]))
}

return jsonObj;

}
}

...In BootStrap...

def gsonBuilder
def init = { servletContext ->
gsonBuilder.registerTypeAdapter(AuthUser.class, new AuthUserSerializer());
}

...In Controller...
def authUser = new AuthUser(username:"someuser", password:"NOT SUPPOSED TO SHOW").save()

// Works fine
render (authUser as GSON) // = {"id":2, "username":"someuser"}

// Serializer not leveraged
render ([profile:memberService.currentMember] as GSON) // = {"profile":{"id":2, "username":"someuser", "password":"NOT SUPPOSED TO SHOW"}}

GSON problem: custom adapters not used

For some reason GSON is failing in get the GsonBuilder from the applicationContext, this causes the class to return a new GsonBuilder() without my custom Adapters. For example:

@Lazy
private GsonBuilder gsonBuilder = {
  GsonBuilder gsonBuilder = applicationContext?.getBean('gsonBuilder', GsonBuilder)
  if(gsonBuilder) {
    println "**** Using applicationContext GsonBuilder"
  } else {
    println "**** Not found bean gsonBuilder"
    gsonBuilder = new GsonBuilder()
  }
  return gsonBuilder
}()

In my case it prints "Not found bean gsonBuilder" and by consequence using render model as GSON fails to use my adapters.

The problem goes away if I explicity declare gsonBuilder in my controller and use render contentType: 'application/json', text: gson.toJson(model)

Maybe it's a problem with ApplicationContextAware? I can also see that applicationContext is null in GSON.

NPE when domain is a HibernateProxy

Using GSON 1.1.4 on Grails 2.1.4

Doing this:
render someInstance as GSON

was producing "cannot get identifier() on null object" in GrailsDomainSerializer.groovy
@ line 71: iterator(domainClass.identifier)

Seems due to a "belongsTo" on the "someInstance" having the classname as a "packageName.DomainName_$$_javassist_5" indicating this is a HibernateProxy

The fix I applied locally is in GrailsDomainSerializer.groovy line 82:

private GrailsDomainClass getDomainClassFor(T instance) {
    // TODO: may need to cache this

    grailsApplication.getDomainClass(getClassOfDomain(instance).name)

}

 private Class getClassOfDomain(T instance) {
    instance instanceof HibernateProxy ? ((HibernateProxy) instance).getHibernateLazyInitializer().getImplementation().class : instance.class
 }

and line 33:
def field = getClassOfDomain(instance).getDeclaredField(property.name)

Sorry about the code inline here I am totally new to git.

Specified dependency format doesn't work

When using compile ":gson:1.1.4" in BuildConfig.
The console output is the following:

Downloading: gson-1.1.4.pom.sha1
:: problems summary ::
:::: ERRORS
        grailsCentral: bad organisation found in http://grails.org/plugins/grails-gson/tags/RELEASE_1_1_4/gson-1.1.4.pom: expected='' found='org.grails.plugins'
| Downloading: gson-1.1.4.pom.sha1
:: problems summary ::
:::: ERRORS
        grailsCentral: bad organisation found in http://grails.org/plugins/grails-gson/tags/RELEASE_1_1_4/gson-1.1.4.pom: expected='' found='org.grails.plugins'
| Downloading: gson-1.1.4.pom.sha1
:: problems summary ::
:::: ERRORS
        grailsCentral: bad organisation found in http://grails.org/plugins/grails-gson/tags/RELEASE_1_1_4/gson-1.1.4.pom: expected='' found='org.grails.plugins'
| Error Failed to resolve dependencies (Set log level to 'warn' in BuildConfig.groovy for more information):

- :gson:1.1.4

Using compile 'org.grails.plugins:gson:1.1.4' works

Serialization options

Gson supports various options for serialization style. The plugin should provide config support for these. If equivalent options exist in the JSON converter then the same config should apply.

GSON unit test controller - failing

I'm trying to write a unit test for a simple controller that takes GSON as input and writes to DB.
GSON because, the input has nested objects.

Controller implementation:

def create() {
    def artist = new Artist(request.GSON)
    if (!artist.save(flush: true)) {
        artist.errors.each {
            log.error("Error while creating Artist " + it)
        }
        render status: 500, layout: null
        return
    }
    response.status = 201
    render artist as GSON
}

Unit test:

@TestMixin(GsonUnitTestMixin)
@TestFor(ArtistController)
@Mock(Artist)
class ArtistControllerTests {
    void testCreate() {
        request.GSON = "{guid:123,"+
                                           "name: 'Akon',"+
                    "albums: ["+
                    "{guid:1,"+
                    "name:'album 1'"+
                    "}]}"
       controller.create()
       assert response.status == 201
      }
   }

Exception:
Cannot get property 'manyToOne' on null object at
def artist = new Artist(request.GSON) in the controller

optional controller template

Would be nice to have the option to install a REST controller template that uses GSON. Can be based on the controller used in the example app.

Register TypeAdapters via spring context

GsonFactory should automatically register any TypeAdapter or TypeAdapterFactory instances found in the spring context. That would enable support for things like Joda Time even when using the GSON converter.

GSON binding is not used by `setProperties`

In the example controller

albumInstance.properties = request.GSON

doesn't bind sub-graphs as it's just using Grails binding not any of the nice stuff done by the plugin.

Fixing this will probably mean iterating over each domain class property and binding it individually in the setProperties meta-method.

Cannot cast GrailsParameterMap to com.google.gson.JsonObject

After installing the plugin it's not possible to set a domain properties with the values of GrailsParameterMap.

The issue can be reproduced in a controller or in a Integration Test

class Test {
  String name
}

class TestTests {
  @Test
  void setDomainProps() {
    Test tst = new Test(name: "x")
    assert tst.save(flush: true)
    MockHttpServletRequest request = RequestContextHolder.currentRequestAttributes().request
    request.setParameter("id", "1")
    request.setParameter("name", "My new name")

    Test.withSession { session ->
      session.flush()
      session.clear()

      GrailsParameterMap params = new GrailsParameterMap(request)
      //cannot cast object with class 'org.codehaus.groovy.grails.web.servlet.mvc.GrailsParameterMap' to class 'com.google.gson.JsonObject'
      tst.properties = params 
      assert teste.save(flush: true)
    }
  }
}

Probably due ArtefactEnhancer#enhanceDomains?

Grails 2.2.1, and also the test must be integration, unit test runs ok...

JDBC Exception while trying to update associations in Many-To-Many Relationship

I have two domain classes:

package com.dipayan

class Author {

    String name
    static hasMany = [books: Book]

    static constraints = {
    }
}

And

package com.dipayan

class Book {

    String name
    int pageCount

    static belongsTo = Author
    static hasMany = [authors: Author]

    static constraints = {
    }
}

and I am using scaffolded controller for both which was generated from gson templates.
After that I inserted 4 authors and 4 books using POST. After the insertion the db content is given below:

mysql> show tables;
+-----------------------+
| Tables_in_person_test |
+-----------------------+
| author                |
| author_books          |
| book                  |
+-----------------------+
3 rows in set (0.00 sec)

mysql> select * from author;
+----+---------+-----------+
| id | version | name      |
+----+---------+-----------+
|  1 |       0 | Author #1 |
|  2 |       0 | Author #2 |
|  3 |       0 | Author #3 |
|  4 |       0 | Author #4 |
+----+---------+-----------+
4 rows in set (0.00 sec)

mysql> select * from book;
+----+---------+---------+------------+
| id | version | name    | page_count |
+----+---------+---------+------------+
|  1 |       0 | Book #1 |        400 |
|  2 |       0 | Book #2 |        400 |
|  3 |       0 | Book #3 |        400 |
|  4 |       0 | Book #4 |        400 |
+----+---------+---------+------------+
4 rows in set (0.00 sec)

After this I tried to create association for one author and three books by doing a PUT request which went through properly.

{
    "id": "1",
    "books" : [
        {"id": 2},
        {"id": 3},
        {"id": 4}
    ]
}

And the DB content is given below after this point.

mysql> select * from author_books;
+---------+-----------+
| book_id | author_id |
+---------+-----------+
|       2 |         1 |
|       3 |         1 |
|       4 |         1 |
+---------+-----------+
3 rows in set (0.00 sec)

Now I tried associating a different Author with the same set of books with a similar PUT request and here the update fails with a JDBC Integrity constraint violation. Below is the given json and the db content after this point.

{
    "id": "2",
    "books" : [
        {"id": 2},
        {"id": 3},
        {"id": 4}
    ]
}
mysql> select * from author_books;
+---------+-----------+
| book_id | author_id |
+---------+-----------+
|       2 |         1 |
|       3 |         1 |
|       4 |         1 |
+---------+-----------+
3 rows in set (0.00 sec)

Error message:

| Error 2013-07-25 22:14:34,990 [http-bio-8080-exec-1] ERROR util.JDBCExceptionReporter  - Duplicate entry '1-3' for key 'PRIMARY'
| Error 2013-07-25 22:14:34,994 [http-bio-8080-exec-1] ERROR events.PatchedDefaultFlushEventListener  - Could not synchronize database state with session
Message: Could not execute JDBC batch update
    Line | Method
->>   61 | update    in com.dipayan.AuthorController
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    195 | doFilter  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter  in grails.plugin.cache.web.filter.AbstractFilter
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    724 | run       in java.lang.Thread

Caused by BatchUpdateException: Duplicate entry '1-3' for key 'PRIMARY'
->> 2054 | executeBatchSerially in com.mysql.jdbc.PreparedStatement
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   1467 | executeBatch in     ''
|    297 | executeBatch in org.apache.commons.dbcp.DelegatingStatement
|     61 | update    in com.dipayan.AuthorController
|    195 | doFilter  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter  in grails.plugin.cache.web.filter.AbstractFilter
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run       in java.util.concurrent.ThreadPoolExecutor$Worker
^    724 | run . . . in java.lang.Thread

Caused by MySQLIntegrityConstraintViolationException: Duplicate entry '1-3' for key 'PRIMARY'
->>  411 | handleNewInstance in com.mysql.jdbc.Util
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    386 | getInstance in     ''
|   1041 | createSQLException in com.mysql.jdbc.SQLError
|   4187 | checkErrorPacket in com.mysql.jdbc.MysqlIO
|   4119 | checkErrorPacket in     ''
|   2570 | sendCommand in     ''
|   2731 | sqlQueryDirect in     ''
|   2815 | execSQL   in com.mysql.jdbc.ConnectionImpl
|   2155 | executeInternal in com.mysql.jdbc.PreparedStatement
|   2458 | executeUpdate in     ''
|   2006 | executeBatchSerially in     ''
|   1467 | executeBatch in     ''
|    297 | executeBatch in org.apache.commons.dbcp.DelegatingStatement
|     61 | update    in com.dipayan.AuthorController
|    195 | doFilter  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter  in grails.plugin.cache.web.filter.AbstractFilter
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run       in java.util.concurrent.ThreadPoolExecutor$Worker
^    724 | run . . . in java.lang.Thread
| Error 2013-07-25 22:14:35,004 [http-bio-8080-exec-1] ERROR errors.GrailsExceptionResolver  - MySQLIntegrityConstraintViolationException occurred when processing request: [PUT] /AuthorTest/api/Author/2
Duplicate entry '1-3' for key 'PRIMARY'. Stacktrace follows:
Message: Duplicate entry '1-3' for key 'PRIMARY'
    Line | Method
->>  411 | handleNewInstance    in com.mysql.jdbc.Util
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    386 | getInstance          in     ''
|   1041 | createSQLException . in com.mysql.jdbc.SQLError
|   4187 | checkErrorPacket     in com.mysql.jdbc.MysqlIO
|   4119 | checkErrorPacket . . in     ''
|   2570 | sendCommand          in     ''
|   2731 | sqlQueryDirect . . . in     ''
|   2815 | execSQL              in com.mysql.jdbc.ConnectionImpl
|   2155 | executeInternal . .  in com.mysql.jdbc.PreparedStatement
|   2458 | executeUpdate        in     ''
|   2006 | executeBatchSerially in     ''
|   1467 | executeBatch         in     ''
|    297 | executeBatch . . . . in org.apache.commons.dbcp.DelegatingStatement
|     61 | update               in com.dipayan.AuthorController
|    195 | doFilter . . . . . . in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter             in grails.plugin.cache.web.filter.AbstractFilter
|   1145 | runWorker . . . . .  in java.util.concurrent.ThreadPoolExecutor
|    615 | run                  in java.util.concurrent.ThreadPoolExecutor$Worker
^    724 | run . . . . . . . .  in java.lang.Thread

I am really in a fix as I am using this in production and is unable to understand if I am doing any wrong.

Cannot install plugin

Hi,

I am using Grails 2.1.0.

First of all, I could not install the plugin using the ZIP file I downloaded.
So, I attempted to created a plugin by myself using packge-plugin command.
Then, I encountered an error "GsonGrailsPlugin.groovy: 27: unable to resolve class ArtefactEnhancer ".

The class "ArtefactEnhancer" is in the test directory.
I moved this class to src directory, and it got packaged without any issue.

Regards,

Handle invalid property names in JSON

RIght now the deserializer will throw an exception if the JSON contains an invalid property name. Instead it should just be ignored (possibly with a warning log).

MockHttpServletRequest Exception on Production mode only

I'm getting this exception on production mode only:

org.springframework.mock.web.MockHttpServletRequest. Stacktrace follows:
java.lang.ClassNotFoundException: org.springframework.mock.web.MockHttpServletRequest

It works fine on development mode.

Serialization does not traverse Hibernate proxies

Any Hibernate proxy is serialized as an empty JSON object. It should either be:

  1. not loaded & serialized as an id only.
  2. loaded & fully serialized.

The options should be controlled by config although 2 is preferable by default.

Make resolveProxies configurable per class

This is not an issue, but a feature request. It would be nice to determine, maybe in the domain class configuration, if a proxy must be resolved or not.

For example:

AccessPerson {
  static belongsTo = [ person: Person, access: Access ]
  ...
}

Person is a big record, so I want to just load his id, but Access is a small record, so I want to resolve the proxy and output all attributes.

Now I can just set the grails.converters.gson.resolveProxies, or am I missing some configuration?

Controller unit tests fail when using the as GSON syntax

When using the render xyz as GSON syntax, I cannot seem to be able to unit test these controllers. Running the unit tests (using Spock) I get the following exception: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'gsonBuilder' is defined

I have defined the gsonBuilder bean using the defineBeans directive and in fact I can unit test the controller if I use the alternative syntax (injecting the gsonBuilder bean, creating a gson object and then calling .toJson(xyz) on it. As the as GSON syntax is shorter and more expressive, I would prefer it over the currently testable one.

In case there is a way to make unit tests work and I just don't know how, maybe the documentation could mention it?

Option to not lazy-load associations?

Not sure how easy or hard it would be to add, but I think it would be great to have an option where a Hibernate proxy that hasn't actually been loaded would just serialize the ID, rather than lazy load the proxy's data and serialize all of it. I did a lot of BlazeDS and Flex work in the past, and one of the biggest issues with BlazeDS was that it blindly loaded and serialized the entire object graph. So it was stunningly easy to send back an object and inadvertently end up loading and serializing a 10,000 node object graph.

GsonUnitTestMixin should be considered only for tests

Because this plugin embeds a GsonUnitTestMixin class which inherits from GsonUnitTestMixin, makes it dependant to grails-test, grails-plugin-testing and spring-test artifacts. This is particularly a problem if someone (me) wants to use this plugin in a mavenized grails project.

If I simply add a dependency to this plugin in the pom.xml file, executing a mvn test leads to a compilation error on GsonUnitTestMixin.groovy because GsonUnitTestMixin class cannot be resolved.

I tried to change the scope of both grails-plugin-testing and grails-test library from test to provided. This helped to build the project, but it brought unwanted (but quite expected) side effects at runtime. After a request to the application, the code which serialize an object to JSON using GSON throws the following:

java.lang.ClassNotFoundException: org.springframework.mock.web.MockHttpServletRequest
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2436)
    at java.lang.Class.getDeclaredMethods(Class.java:1793)
    at org.codehaus.groovy.util.LazyReference.getLocked(LazyReference.java:46)
    at org.codehaus.groovy.util.LazyReference.get(LazyReference.java:33)
    at grails.plugin.gson.converters.GSON.render(GSON.groovy:57)
    at grails.plugin.gson.api.ArtefactEnhancer$_enhanceControllers_closure1.doCall(ArtefactEnhancer.groovy:29)
    ...

I wonder if their is a way to only consider the GsonUnitTestMixin class just for tests.

Dependency on GrailsWebUtil without exporting spring-test

The GSON converter (and maybe other places, not sure) has a dependency on GrailsWebUtil on or about line 57. GrailsWebUtil has a dependency on spring test, but you're not exporting the dependency in your plugin. If a user isn't utilizing spring-test, or they're not exporting it at run time, that results in a ClassNotFound error being thrown from GrailsWebUtils. To resolve (as many people have reported) you have to list a runtime dependency as follows:

runtime 'org.springframework:spring-test:3.2.8.RELEASE'

Several other people have reported this in a round-about way, but I don't think they're reporting it very clearly. They state it only affects war runs, etc. To be clear, so far as I can tell this affects ANY running application in dev or production mode that doesn't explicitly declare that dependency or get it transitively. I'm assuming your test app is getting it transitively from the spock dependency you have in the plugin, but I'm not positive about that.

I'd submit a patch, but I don't know how you want to fix it. The way I see it there are a few options:

  1. push it back up to the grails team as most people aren't going to be deploying spring-test jars with their war file, but they certainly might be using GrailsWebUtils. One could argue it's inappropriate to have Mocking functionality in that class since it's used in production code as well.
  2. Export the dependency from your plugin so new installs get it out the gate. May be an issue of versions though if they're also using spring-test
  3. List the dependency in the documentation / configuration.
  4. Change the plugin to get the content-type some other way.

Thoughts?

Error when using version 1.1.4, from grails plugins, but not happen in master branch

When I use compile 'org.grails.plugins:gson:1.1.4' in my BuildConfig.groovy, I receive an error:

| Error 2013-10-03 15:27:20,323 [http-bio-8080-exec-2] ERROR errors.GrailsExceptionResolver  - NoSuchFieldException occurred when processing request: [POST] /closer-server/import/activitiesNear.json - parameters:
owner_ids: 100003458440561,371982050
distance: 10000
origin: facebook,instagram
longitude: -48.8209441
to_date: 2013/10/03 03:27
latitude: -26.27844889999997
from_date: 2013/09/26 03:27
access_tokens: CAAHQhYZBhez0BANbqG7stscNM79MWEbd7fWxCgL7USbPLCTniZB6rN8xGZAiESS6L3lMPlOEZBAUlctCZAw89QvuWXRWAkIuGleRHhSZBTyAThzu0rL8jLaDuMXPQkMTmSZBJ5OoavbM580sxqnFbA4ZBSBJwsnUdZAVC8HfOOrkZANEz2v30rrNL0U2t44gu9NpEBLNZCZBMa0fdZBlEEN2ipxqihJjM62B2kOQZD,371982050.2a77e46.14d3c41eef9240978b4b51735b744cb4
id. Stacktrace follows:
Message: id
    Line | Method
->> 1884 | getDeclaredField      in java.lang.Class
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|     28 | doCall                in grails.plugin.gson.adapters.GrailsDomainSerializer$_serialize_closure1
|     59 | doCall . . . . . . .  in grails.plugin.gson.adapters.GrailsDomainSerializer$_eachUnvisitedProperty_closure2
|     66 | eachProperty          in grails.plugin.gson.adapters.GrailsDomainSerializer
|     51 | eachUnvisitedProperty in     ''
|     27 | serialize             in     ''
|     70 | write . . . . . . . . in com.google.gson.TreeTypeAdapter
|     63 | write                 in     ''
|     68 | write . . . . . . . . in com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper
|     96 | write                 in com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter
|     60 | write . . . . . . . . in     ''
|    586 | toJson                in com.google.gson.Gson
|    479 | toJsonTree . . . . .  in     ''
|    458 | toJsonTree            in     ''
|     29 | wrapJsonInRootKey . . in br.com.informant.closer.utils.JsonUtils
|     64 | activitiesNear        in br.com.informant.closer.controller.ImportController
|    195 | doFilter . . . . . .  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter              in grails.plugin.cache.web.filter.AbstractFilter
|    895 | runTask . . . . . . . in java.util.concurrent.ThreadPoolExecutor$Worker
|    918 | run                   in     ''
^    662 | run . . . . . . . . . in java.lang.Thread

But when I clone the master branch, at the moment in 60df1f0, and use grails.plugin.location.gson = '../../grails-gson/', this don't happen.

I think that this was already resolved in master, but the grails-gson version in grails plugins repository is outdated. And need to be updated.

Thanks in advance!

Deserialization does not wire up bidirectional relationships properly

When instances are deserialized the reverse side of the relationship needs to be assigned (or appropriate addTo method used). Otherwise the binding succeeds & validate / save appear to work but an exception is thrown when the session is flushed.

I thought since I'm using DataBindingUtils.bindObjectToDomainInstance this would be handled properly but it does not appear to be the case. The test app wasn't catching the problem as the Album -> Artist relationship isn't bidirectional.

Using an id that can be bindable will throw java.lang.NumberFormatException

When using parseRequest:true in UrlMappings the GSON.parse() method is parsing the id as a Double, causing problems when you create the instance from the params map and your domain class can have the id bindable (I need this for legacy databases).

class MyDomainClass {
  String descr

  static constraints = {
    id bindable: true, generator: 'assigned'
  }

}

JSON POST
{"id":77,"desc":"Test"}

MyDomainClass domain = new MyDomainClass(params) //java.lang.NumberFormatException

It would be nice to convert the id to the Long, or his declared type, instead of using Google's default.

I also tried creating the instance from request.GSON, but it returns null (still don't figured out why).

StackOverflowError serializing transient instances

With a simple domain class:

class Contact {
  String name
  String phone
  static constraints = {
     name blank:false, maxSize: 255
     email blank:false, maxSize: 255, unique: true
  }
}

And a controller save method:

def save() {
  Contact contact = new Contact(name: params.name, phone: params.phone, email: params.email)
  if(contact.save(flush:true)) {
    def pars = [success: false, contatos: contact]
    render pars as GSON
  } else {
    println contact.errors
    def pars = [success: false, contatos: contact] 
    render pars as GSON
  }
}

I'm getting StackOverflowError if I try to serialize a non-saved instance. This can be simulated in an Integration Test:

void testNewElement() {
  contactController.params.name = "teste"
  contactController.save()
}

Save is not cascaded to sub-graph

When creating a new object with new members of a persistent collection an exception is thrown on save unless cascade: 'all' is applied. According to the Grails docs that should only be necessary in the absence of a belongsTo property (see cascade)

This may be a Grails bug.

error running grails compile : unable to resolve class org.apache.commons.beanutils.PropertyUtils

Getting this error: unable to resolve class org.apache.commons.beanutils.PropertyUtils

when running: grails compile
grails version: 2.4.0

I am admittedly very new to grails but learning!!
Thank you!

error details:

| Compiling 14 source files

| Compiling 14 source files

| Compiling 15 source files
  [groovyc] org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
  [groovyc] /Users/marknickel/mainline/development/github/grails-gson/src/groovy/grails/plugin/gson/adapters/GrailsDomainSerializer.groovy: 7: unable to resolve class org.apache.commons.beanutils.PropertyUtils
  [groovyc]  @ line 7, column 1.
  [groovyc]    import org.apache.commons.beanutils.PropertyUtils
  [groovyc]    ^
  [groovyc]
| Compiling 15 source files.
| Error Compilation error: startup failed:
/Users/marknickel/mainline/development/github/grails-gson/src/groovy/grails/plugin/gson/adapters/GrailsDomainSerializer.groovy: 7: unable to resolve class org.apache.commons.beanutils.PropertyUtils
 @ line 7, column 1.
   import org.apache.commons.beanutils.PropertyUtils
   ^

1 error

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.