GrailsでJSONの独自Marshallerを登録する

元ネタ
http://compiledammit.com/2012/08/16/custom-json-marshalling-in-grails-done-right/

有名な話なのかもしれませんが、独自Marshaller登録できるんですね。知らなかった。

GrailsではJSONレスポンス書き出し方に代表的な2つのやり方が存在します。
http://grails.org/doc/latest/guide/single.html#xmlAndJSON

  • renderメソッド使う方法
  • 自動Marshallingする方法

以下の様なドメインがあったとして

class Author {

    String name
    String email

    static hasMany = [books: Book]
}
class Book {

    String title

    static belongsTo = Author

}

これを後者の方はこんなかんじに書けます。

render Author.list() as JSON

実際に出力されるJSONは以下のようになります。

[
  {
    "class":"org.yamkazu.Author",
    "id":1,
    "books":[{"class":"Book","id":2},{"class":"Book","id":1}],
    "name":"test1",
    "email":"test1@example.com"
  },{
    "class":"org.yamkazu.Author",
    "id":2,
    "books":[],"name":"test2",
    "email":"test2@example.com"
  }
]

as JSONを使うと手軽な分、classとか必要ない情報が含まれてしまったり、例えばemailとかユーザは見せてはいけないのに、ドメインに含まれているため出力されてしまうといったように、気軽にas JSONすると良からぬ事態を引き起こしかねません。また関連ドメインのプロパティはidしか情報が出力されないといった制約もあります。

そこで独自のMarshallerです。grails.converters.JSON#registerObjectMarshallerのメソッドで登録できます。
f:id:yamkazu:20120831231539p:plain

一番手軽なのはクラスと、Closureを引数に取るやつです。こいつをBootstrapでも登録してもいいし、最初に紹介したURLのように独自のSpringBeanで登録させても良いです。

今回はemailを外部に暴露したくなかったのと、classは不要なので除去、あと関連先のbookのtitleまで含みたいので以下の様に登録してみました。

JSON.registerObjectMarshaller(Author) { Author author ->
    return [
        id: author.id,
        name: author.name,
        books: author.books.collect { Book book ->
            [
                id: book.id,
                title: book.title
            ]
        }
    ]
}

これで出力されるJSONは以下のようになります。

[
  {
    "id":1,
    "name":"test1",
    "books":[{"id":2,"title":"title1"},{"id":1,"title":"title2"}]
  },{
    "id":2,
    "name":"test2",
    "books":[]
  }
]

複数箇所で該当ドメインクラスのJSONレンダリングを必要とするといった場合や、絶対に外に漏れていけないプロパティがある場合に有効な手段じゃないでしょうか!