jsend_wrapper-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,306 @@
1
+ # Rails JSend Wrapper
2
+
3
+ ## Usage
4
+
5
+ Add the following lines to your Gemfile:
6
+
7
+ ```ruby
8
+ gem 'jsend_wrapper-rails'
9
+ gem 'jbuilder' # optional
10
+ ```
11
+
12
+ ### render jsend: ...
13
+
14
+ ```ruby
15
+ # Success
16
+ render jsend: @object
17
+ render jsend: { success: @object }
18
+
19
+ # Fail
20
+ render jsend: { fail: 'too bad' }
21
+
22
+ # Error
23
+ render jsend: { error: 'too bad' }
24
+ render jsend: { error: 'too bad', code: 123 }
25
+ render jsend: { error: 'too bad', data: @object }
26
+ render jsend: { error: 'too bad', code: 123, data: @object }
27
+ ```
28
+
29
+ The elements `code` and `data` are optional for JSend Error containers. If you
30
+ leave them out, they will be absent from the rendered JSON. Note the differences
31
+ in these two examples:
32
+
33
+ `render jsend: {error: 'too bad'}`
34
+
35
+ **Result**:
36
+ ```json
37
+ {
38
+ "status": "error",
39
+ "message": "too bad"
40
+ }
41
+ ```
42
+
43
+ `render jsend: {error: 'too bad', data: nil}`
44
+
45
+ **Result**:
46
+ ```json
47
+ {
48
+ "status": "error",
49
+ "message": "too bad",
50
+ "data": null
51
+ }
52
+ ```
53
+
54
+
55
+ ### Templates (optional)
56
+
57
+ If [JBuilder](https://rubygems.org/gems/jbuilder) is available, you can create
58
+ view files with a `.jsend` (example: `app/views/posts/show.json.jsend`)
59
+ extension. These views are rendered by `Jbuilder`, exactly as if they were
60
+ named with a `.jbuilder` extension instead. These views will be wrapped in a
61
+ JSend Success container.
62
+
63
+ **Example**: `app/views/posts/show.json.jsend`
64
+ ```ruby
65
+ json.title 'The Pragmatic Programmer'
66
+ json.year 1999
67
+ ```
68
+
69
+ **Result**:
70
+ ```json
71
+ {
72
+ "status": "success",
73
+ "data": {
74
+ "title": "The Pragmatic Programmer",
75
+ "year": 1999
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### Tips
81
+
82
+ #### Handling Errors
83
+
84
+ You can use `rescue_from` to automatically handle errors:
85
+
86
+ ```ruby
87
+ rescue_from ActiveRecord::RecordNotFound do
88
+ render status: :not_found, jsend: {
89
+ error: 'record not found', code: 404, data: { id: params[:id] } }
90
+ end
91
+ ```
92
+
93
+ **Example**: `GET /posts/1234.json`:
94
+ ```json
95
+ {
96
+ "status": "error",
97
+ "message": "record not found",
98
+ "code": 404,
99
+ "data": {
100
+ "id": "1234"
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## JSend Spec
106
+
107
+ * **What?** - Put simply, JSend is a specification that lays down some rules
108
+ for how [JSON](http://json.org) responses from web servers should be
109
+ formatted. JSend focuses on application-level (as opposed to protocol- or
110
+ transport-level) messaging which makes it ideal for use in
111
+ [REST](http://en.wikipedia.org/wiki/Representational_State_Transfer)-style
112
+ applications and APIs.
113
+ * **Why?** - There are lots of web services out there providing JSON data,
114
+ and each has its own way of formatting responses. Also, developers writing
115
+ for JavaScript front-ends continually re-invent the wheel on communicating
116
+ data from their servers. While there are many common patterns for
117
+ structuring this data, there is no consistency in things like naming or types
118
+ of responses. Also, this helps promote happiness and unity between backend
119
+ developers and frontend designers, as everyone can come to expect a common
120
+ approach to interacting with one another.
121
+ * **Hold on now, aren't there already specs for this kind of thing?** -
122
+ Well... no. While there are a few handy specifications for dealing with JSON
123
+ data, most notably [Douglas Crockford](http://www.crockford.com/)'s
124
+ [JSONRequest](http://www.json.org/JSONRequest.html) proposal, there's nothing
125
+ to address the problems of general application-level messaging. More on this
126
+ later.
127
+ * **(Why) Should I care?** - If you're a library or framework developer, this
128
+ gives you a consistent format which your users are more likely to already be
129
+ familiar with, which means they'll already know how to consume and interact
130
+ with your code. If you're a web app developer, you won't have to think about
131
+ how to structure the JSON data in your application, and you'll have existing
132
+ reference implementations to get you up and running quickly.
133
+ * **Discuss** - [Mailing list](http://lists.omniti.com/mailman/listinfo/jsend-users)
134
+
135
+ ### So how's it work?
136
+
137
+ A basic JSend-compliant response is as simple as this:
138
+ ```json
139
+ {
140
+ "status": "success",
141
+ "data": {
142
+ "post": {
143
+ "id": 1,
144
+ "title": "A blog post",
145
+ "body": "Some useful content"
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ When setting up a JSON API, you'll have all kinds of different types of calls
152
+ and responses. JSend separates responses into some basic types, and defines
153
+ required and optional keys for each type:
154
+
155
+ <table>
156
+ <thead>
157
+ <tr>
158
+ <th>Type</th>
159
+ <th>Description</th>
160
+ <th>Required Keys</th>
161
+ <th>Optional Keys</th>
162
+ </tr>
163
+ </thead>
164
+ <tbody>
165
+ <tr>
166
+ <td>success</td>
167
+ <td>All went well, and (usually) some data was returned.</td>
168
+ <td>status, data</td>
169
+ <td></td>
170
+ </tr>
171
+ <tr>
172
+ <td>fail</td>
173
+ <td>
174
+ There was a problem with the data submitted, or some pre-condition of
175
+ the API call wasn't satisfied.
176
+ </td>
177
+ <td>status, message</td>
178
+ <td></td>
179
+ </tr>
180
+ <tr>
181
+ <td>error</td>
182
+ <td>
183
+ An error occurred in processing the request, i.e. an exception was
184
+ thrown.
185
+ </td>
186
+ <td>status, message</td>
187
+ <td>code, data</td>
188
+ </tr>
189
+ </tbody>
190
+ </table>
191
+
192
+ ### Example response types
193
+
194
+ **Success**: When an API call is successful, the JSend object is used as a
195
+ simple envelope for the results, using the {{{data}}} key, as in the following:
196
+
197
+ **`GET /posts.json`:**
198
+ ```json
199
+ {
200
+ "status": "success",
201
+ "data": [
202
+ {
203
+ "id": 1,
204
+ "title": "A blog post",
205
+ "body": "Some useful content"
206
+ },
207
+ {
208
+ "id": 2,
209
+ "title": "Another blog post",
210
+ "body": "More content"
211
+ },
212
+ ]
213
+ }
214
+ ```
215
+
216
+ **`GET /posts/2.json`:**
217
+ ```json
218
+ {
219
+ "status": "success",
220
+ "data": {
221
+ "id": 2,
222
+ "title": "Another blog post",
223
+ "body": "More content"
224
+ }
225
+ }
226
+ ```
227
+
228
+ **`DELETE /posts/2.json`:**
229
+ ```json
230
+ {
231
+ "status": "success",
232
+ "data": null
233
+ }
234
+ ```
235
+
236
+ Required keys:
237
+ * status: Should always be set to "success".
238
+ * data: Acts as the wrapper for any data returned by the API call. If the call
239
+ returns no data (as in the last example), data should be set to null.
240
+
241
+ **Fail**: When an API call is rejected due to invalid data or call conditions,
242
+ the JSend object's data key contains an object explaining what went wrong,
243
+ typically a hash of validation errors. For example:
244
+
245
+ **`POST /posts.json`** (with data `body: "Trying to creating a blog post"`):
246
+ ```json
247
+ {
248
+ "status": "fail",
249
+ "data": {
250
+ "title": "A title is required"
251
+ }
252
+ }
253
+ ```
254
+
255
+ Required keys:
256
+ * status: Should always be set to "fail".
257
+ * data: Again, provides the wrapper for the details of why the request failed.
258
+ If the reasons for failure correspond to `POST` values, the response
259
+ object's keys *should* correspond to those `POST` values.
260
+
261
+ **Error**: When an API call fails due to an error on the server. For example:
262
+
263
+ **`GET /posts.json`**:
264
+ ```json
265
+ {
266
+ "status" : "error",
267
+ "message" : "A title is required"
268
+ }
269
+ ```
270
+
271
+ Required keys:
272
+ * status: Should always be set to "error".
273
+ * message: A meaningful, end-user-readable (or at the least log-worthy)
274
+ message, explaining what went wrong.
275
+
276
+ Optional keys:
277
+ * code: A numeric code corresponding to the error, if applicable
278
+ * data: A generic container for any other information about the error, i.e. the
279
+ conditions that caused the error, stack traces, etc.
280
+
281
+ ### Whither HTTP?
282
+
283
+ But wait, you ask, doesn't HTTP already provide a way to communicate response
284
+ statuses? Why yes, astute reader, it does. So how does the notion of
285
+ indicating response status in the message body fit within the context of HTTP?
286
+ Two things:
287
+ * The official HTTP spec has 41 status codes, and there are many
288
+ interpretations on how to use each one. JSend, on the other hand, defines a
289
+ more constrained set of status codes, specifically related to handling JSON
290
+ traffic in the context of a dynamic web UI.
291
+ * The spec is meant to be as small, constrained, and generally-applicable as
292
+ possible. As such, it has to be somewhat self-contained. A common pattern
293
+ for implementing JSON services is to load a JavaScript file which passes a
294
+ JSON block into a user-specified callback. JSON-over-XHR handling in many
295
+ JavaScript frameworks follows similar patterns. As such, the end-user
296
+ (developer) never has a chance to access the HTTP response itself.
297
+
298
+ So where does that leave us? Accounting for deficiencies in the status quo does
299
+ not negate the usefulness of HTTP compliance. Therefore it is advised that
300
+ server-side developers use both: provide a JSend response body, and whatever
301
+ HTTP header(s) are most appropriate to the corresponding body.
302
+
303
+ ### License
304
+
305
+ The JSend specification (this page) is covered under a
306
+ [License modified BSD License](http://labs.omniti.com/labs/jsend/wiki/License)
data/Rakefile ADDED
@@ -0,0 +1,65 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ require 'rubygems'
17
+ require 'bundler'
18
+ require 'rake'
19
+ require 'jeweler'
20
+
21
+ require_relative 'lib/jsend_wrapper/version'
22
+
23
+ begin
24
+ Bundler.setup :default, :development
25
+ rescue Bundler::BundlerError => e
26
+ $stderr.puts e.message
27
+ $stderr.puts 'Run `bundle install` to install missing gems'
28
+ exit e.status_code
29
+ end
30
+
31
+ Jeweler::Tasks.new do |gem|
32
+ gem.name = 'jsend_wrapper-rails'
33
+ gem.version = JsendWrapper::Version.to_s
34
+ gem.homepage = 'http://github.com/sangster/jsend_wrapper-rails'
35
+ gem.license = 'GPL 3'
36
+ gem.summary = 'Wraps JSON in a JSend envelope'
37
+ gem.description = <<-EOF.strip.gsub /\s+/, ' '
38
+ Some description here.
39
+ EOF
40
+ gem.email = 'jon@ertt.ca'
41
+ gem.authors = ['Jon Sangster']
42
+
43
+ gem.files = Dir['{lib}/**/*', 'Rakefile', 'README.md', 'LICENSE']
44
+ gem.files.exclude 'lib/jsend_wrapper/version.rb'
45
+ end
46
+
47
+ Jeweler::RubygemsDotOrgTasks.new
48
+
49
+ task :pry do
50
+ exec 'pry --gem'
51
+ end
52
+
53
+ task bump: ['bump:patch']
54
+ namespace :bump do
55
+ [:major, :minor, :patch].each do |part|
56
+ bumper = JsendWrapper::Version::Bumper.new part
57
+ desc "Bump the version to #{bumper}"
58
+ task(part) { bumper.bump }
59
+ end
60
+ end
61
+
62
+ # RSpec
63
+ require 'rspec/core/rake_task'
64
+ RSpec::Core::RakeTask.new :spec
65
+ task default: :spec
@@ -0,0 +1,58 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ require 'rails/railtie'
17
+
18
+ module JsendWrapper
19
+ module Rails
20
+ class Railtie < ::Rails::Railtie
21
+ initializer 'jsend_wrapper-rails.initialization' do
22
+ if JsendWrapper::Rails.jbuilder_available?
23
+ JsendWrapper::Rails.install_template
24
+ end
25
+ JsendWrapper::Rails.install_render_option
26
+ end
27
+ end
28
+
29
+
30
+ #@return [Boolean] true if the {Jbuilder} gem is available
31
+ def self.jbuilder_available?
32
+ require 'jbuilder'
33
+ true
34
+ rescue LoadError
35
+ STDERR.puts 'WARN: Please include the "jbuilder" gem for .jsend templates'
36
+ false
37
+ end
38
+
39
+ # Install a "template handler" for .jsend view files. These files will be
40
+ # processed with {Jbuilder}, just like .jbuilder view files, but the result
41
+ # will be wrapped in a "success" JSend wrapper.
42
+ def self.install_template
43
+ ActionView::Template.register_template_handler \
44
+ :jsend, JsendWrapper::Rails::TemplateHandler
45
+ end
46
+
47
+
48
+ # Adds the "jsend:" option to {ActiveController::Base#render}
49
+ def self.install_render_option
50
+ require_relative 'render_option'
51
+
52
+ ActionController::Renderers.add :jsend do |value, _|
53
+ self.content_type ||= Mime::JSON
54
+ RenderOption.new(value).render
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,76 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ require_relative '../renderers'
17
+
18
+ module JsendWrapper
19
+ # Parses the "render jsend: {...}" command. Valid forms:
20
+ class RenderOption
21
+ VALID_TYPES = [:success, :fail, :error]
22
+ VALID_OPTIONS = [:data, :code, :message]
23
+ VALID_KEYS = VALID_TYPES + VALID_OPTIONS
24
+
25
+ #@param obj [Hash,Object] Examples:
26
+ # render jsend: @object
27
+ # render jsend: {success: @object}
28
+ # render jsend: {fail: @object}
29
+ # render jsend: {error: @object}
30
+ # render jsend: {error: @object, code: 123}
31
+ # render jsend: {error: @object, data: @another_object}
32
+ # render jsend: {error: @object, code: 123, data: @another_object}
33
+ def initialize(obj)
34
+ @hash = normalize obj
35
+ end
36
+
37
+
38
+ #@return [String] A string containing the rendered JSON
39
+ def render
40
+ renderer.call
41
+ end
42
+
43
+
44
+ #@return [Renderer] One of {SuccessRenderer}, {FailRenderer}, or
45
+ # {ErrorRenderer}
46
+ def renderer
47
+ @renderer ||=
48
+ if @hash.key? :success
49
+ SuccessRenderer.new @hash[:success]
50
+ elsif @hash.key? :fail
51
+ FailRenderer.new @hash[:fail]
52
+ elsif @hash.key? :error
53
+ ErrorRenderer.new @hash[:error], @hash.slice(:code, :data)
54
+ else
55
+ raise 'render jsend:{...} must include "success:", "fail:", or "error:"'
56
+ end
57
+ end
58
+
59
+
60
+ private
61
+
62
+
63
+ def normalize(obj)
64
+ if Hash === obj && jsend_hash?(obj)
65
+ obj
66
+ else
67
+ {success: obj}
68
+ end
69
+ end
70
+
71
+
72
+ def jsend_hash?(hash)
73
+ hash.keys.all? { |key| VALID_KEYS.include? key }
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,43 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ module JsendWrapper
17
+ module Rails
18
+ class TemplateHandler
19
+ def self.call(template)
20
+ json_handler = ActionView::Template.registered_template_handler :jbuilder
21
+ json = json_handler.call template
22
+
23
+ <<-RUBY
24
+ content = instance_eval #{json.inspect}
25
+ JsendWrapper::Handlers::Success.new(self).render content
26
+ RUBY
27
+ end
28
+
29
+
30
+ #@param view [ActiveView::Base]
31
+ def initialize(view)
32
+ @view = view
33
+ end
34
+
35
+
36
+ #@param json [String]
37
+ def render(json, *)
38
+ json = 'null' if !json || json.empty?
39
+ %[{"status":"success","data":#{json}}]
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,67 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ require_relative 'renderer'
17
+
18
+ module JsendWrapper
19
+ # Wraps the given message in a JSend Error. JSend Errors have two required
20
+ # elements (status, message) and two optional elements (code, data).
21
+ class ErrorRenderer < Renderer
22
+ attr_accessor :message, :has_code, :code, :has_data, :data
23
+
24
+ alias_method :code?, :has_code
25
+ alias_method :data?, :has_data
26
+
27
+ #@param message [#to_s]
28
+ #@param optional [Hash] the optional elements
29
+ #@option optional [#to_i] :code a numeric code representing the error
30
+ #@option optional [Object] :data a generic container for any other
31
+ # information about the error
32
+ def initialize(message, optional)
33
+ self.message = message.to_s
34
+ self.has_code = optional.key? :code
35
+ self.has_data = optional.key? :data
36
+
37
+ self.code = parse_code optional[:code] if code?
38
+ self.data = optional[:data] if data?
39
+ end
40
+
41
+
42
+ #@return [String] the rendered JSON
43
+ def call
44
+ %[{"status":"error","message":#{message.to_json}#{optional}}]
45
+ end
46
+
47
+
48
+ private
49
+
50
+
51
+ def parse_code(code)
52
+ if code.respond_to? :to_i
53
+ code.to_i
54
+ else
55
+ raise '"code" must respond to #to_i'
56
+ end
57
+ end
58
+
59
+
60
+ def optional
61
+ ''.tap do |json|
62
+ json << ',"code":' << code.to_s if code?
63
+ json << ',"data":' << json_string(data) if data?
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,32 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ require_relative 'renderer'
17
+
18
+ module JsendWrapper
19
+ # Wraps the given message in a JSend Failure. JSend Failures have two required
20
+ # elements (status, data).
21
+ class FailRenderer < Renderer
22
+ attr_accessor :data
23
+
24
+ def initialize(obj)
25
+ self.data = obj
26
+ end
27
+
28
+ def call
29
+ %[{"status":"fail","data":#{json_string data}}]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ module JsendWrapper
17
+ class Renderer
18
+ protected
19
+
20
+ def json_string(obj)
21
+ if obj.respond_to? :to_json
22
+ obj.to_json
23
+ elsif obj.nil?
24
+ 'null'
25
+ else
26
+ obj.to_s.to_json
27
+ end
28
+ end
29
+ end
30
+ end