├── .gitignore
├── .travis.yml
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── jsonify.gemspec
├── lib
├── jsonify.rb
└── jsonify
│ ├── blank_slate.rb
│ ├── builder.rb
│ ├── generate.rb
│ ├── json_value.rb
│ ├── template.rb
│ ├── tilt.rb
│ └── version.rb
├── performance
├── profile.rb
└── speed.rb
└── spec
├── builder_spec.rb
├── generate_spec.rb
├── hello_world.jsonify
├── json_value_spec.rb
├── jsonify_spec.rb
├── spec_helper.rb
└── template_spec.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | .bundle
3 | Gemfile.lock
4 | pkg/*
5 | .yardoc/
6 | doc/
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - 1.9.3
3 | - 1.9.2
4 | - 1.8.7
5 | - ree
6 | - rbx
7 | - jruby
8 |
9 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | # Gem dependencies are in jsonify.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2011 Bill Siggelkow
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Jsonify — a builder for JSON [](http://travis-ci.org/bsiggelkow/jsonify)
2 |
3 | [Jsonify](https://github.com/bsiggelkow/jsonify) is to JSON as [Builder](https://github.com/jimweirich/builder) is to XML.
4 |
5 | To use Jsonify for Rails templates, install [Jsonify-Rails](https://github.com/bsiggelkow/jsonify-rails).
6 |
7 | ## Goal
8 |
9 | Jsonify provides a ___builder___ style engine for creating correct JSON representations of Ruby objects entirely based on the [JSON specification](http://json.org).
10 |
11 | ## Motivation
12 |
13 | JSON and XML are the most common data representation formats used by RESTful applications. Jsonify is built on the belief that these representations belong in the ___view___ layer of the application.
14 |
15 | Jsonify also seeks to emulate the simplicity of [Builder](https://github.com/jimweirich/builder). I have not found a single builder engine for JSON that provides a Builder-like experience. Jsonify is my attempt to remedy that situation.
16 |
17 | ### Rails
18 |
19 | Rails made XML generation easy with support for Builder templates, but, for JSON, there was no clear approach. (Note: Rails has since extracted [jbuilder](https://github.com/rails/jbuilder).)
20 |
21 | For many applications, particularly those based on legacy a database, it is common to expose the data in a more client-friendly format than what is generated by the default Rails `to_json` method.
22 |
23 | ### See Also
24 |
25 | There are a number of [related libraries](#related) available that try to solve this problem. Some of which take a similar approach to Jsonify and provide a builder-style interface. Others allow the developer to specify the representation using a common DSL that can generate both JSON and XML. Please take a look at these projects and consider your alternatives. It's my opinion that there are substantial and inherent differences between XML and JSON; and that these differences may force the developer to make concessions in one interface or the other.
26 |
27 |
28 | ## Installation
29 |
30 | `gem install jsonify`
31 |
32 | ## Usage
33 |
34 | _Note: JSON output is usually shown "prettified" in examples. This is only for illustration purposes, as the Jsonify default is `plain` compact string without newlines. Enable pretty output by passing `:format => :pretty` to the Jsonify::Builder constructor. Keep in mind that pretty printing is a relatively costly operation so use it only when neccessary._
35 |
36 | ### Compatibility Warning
37 |
38 | #### Arrays
39 |
40 | Array handling was changed in 0.2.0 to provide a more natural feel. Code written using earlier versions of Jsonify may not work correctly. Previously you had to switch to a more conventional Ruby style for arrays:
41 |
42 | ```ruby
43 | json.links(@links) do |link|
44 | {:rel => link.type, :href => link.url}
45 | end
46 | ```
47 |
48 | This syntax was a frequent stumbling block with users. The interface for handling arrays is now consistent with the builder-style and should be less surprising:
49 |
50 | ```ruby
51 | json.links(@links) do |link|
52 | json.rel link.type
53 | json.href link.url
54 | end
55 | ```
56 |
57 | _As always, all feedback is greatly appreciated. I want to know how this new style works out._
58 |
59 | ### Quick Example
60 |
61 | Using Jsonify to create some objects to represent a person and associated hyperlinks:
62 |
63 | ```ruby
64 | @person = Struct.new(:first_name,:last_name).new('George','Burdell')
65 |
66 | Link = Struct.new(:type, :url)
67 |
68 | @links = [
69 | Link.new('self', 'http://example.com/people/123'),
70 | Link.new('school', 'http://gatech.edu')
71 | ]
72 |
73 | # Build this information as JSON
74 | require 'jsonify'
75 | json = Jsonify::Builder.new(:format => :pretty)
76 |
77 | # Representation of the person
78 | json.alumnus do
79 | json.fname @person.first_name
80 | json.lname @person.last_name
81 | end
82 |
83 | # Relevant links
84 | json.links(@links) do |link|
85 | json.rel link.type
86 | json.href link.url
87 | end
88 |
89 | # Evaluate the result to a string
90 | json.compile!
91 | ```
92 |
93 | Gives you this JSON:
94 |
95 | ```json
96 | {
97 | "alumnus": {
98 | "fname": "George",
99 | "lname": "Burdell"
100 | },
101 | "links": [
102 | {
103 | "rel": "self",
104 | "href": "http://example.com/people/123"
105 | },
106 | {
107 | "rel": "school",
108 | "href": "http://gatech.edu"
109 | }]
110 | }
111 | ```
112 |
113 | #### Convenience methods
114 |
115 | Jsonify provides class-level convenience methods that
116 | save you the trouble of instantiating the `Jsonify::Builder`. Each of these methods accepts a block, yields a new `Builder` object to the block, and then compiles the result.
117 |
118 | - `compile`
119 | - Compiles the given block; any options are passed to the instantiated `Builder`.
120 | - `pretty`
121 | - Compiles the given block; results are output in `pretty` format.
122 | - `plain`
123 | - Compiles the given block; results are output in `plain` (default) format.
124 |
125 | ```ruby
126 | Jsonify::Builder.plain do |j|
127 | j.song 'Fearless'
128 | j.album 'Meddle'
129 | end
130 | ```
131 |
132 | ### Rails View Templates
133 |
134 | Jsonify can be used for Rails 3 view templates via the [jsonify-rails](https://github.com/bsiggelkow/jsonify-rails) which includes a Rails 3 template handler. Any template with a `.jsonify` extension will be handled by Rails.
135 |
136 | The Jsonify template handler exposes the `Jsonify::Builder` instance to your template with the `json` variable:
137 |
138 | ```ruby
139 | json.hello do
140 | json.world "Jsonify is Working!"
141 | end
142 | ```
143 |
144 | Your Jsonify template will have access to any instance variables that are exposed through the controller. See [Jsonify-Rails](https://github.com/bsiggelkow/jsonify-rails) for additional details.
145 |
146 | #### Partials
147 |
148 | Jsonify views can use other Jsonify partials. How the template uses a partial depends on the returned string. _Remember that partials always return strings as their results._
149 |
150 | ##### Jsonify partials
151 |
152 | Jsonify partials are files that have a `.jsonify` extension. Partials __must__ return a valid JSON string. The string should represent a JSON object (wrapped in curly braces `{}`) or a JSON array (wrapped in square brackets `[]`).
153 |
154 | Use the `ingest!` method to use a partial in your template:
155 |
156 | ```ruby
157 | json.ingest! (render :partial => 'my_partial')
158 | ```
159 |
160 | `ingest!` parses the JSON into a Jsonify object graph, and then adds it to the builder.
161 |
162 | In your main template:
163 |
164 | ```ruby
165 | # index.jsonify
166 |
167 | json << 1
168 | json.ingest! (render :partial => 'my_partial')
169 | ```
170 |
171 | The first line creates an array using the append `<<` operator. The second line shows how the partial is used. You cannot simply place `render :partial ...` on a line by itself as you can do with other templates like `erb` and `haml`. You have to explicitly tell Jsonify to add it to the builder.
172 |
173 | Here's a partial:
174 |
175 | ```ruby
176 | # _my_partial.jsonify
177 |
178 | json << 3
179 | json << 4
180 | ```
181 |
182 | This `json` variable in this partial is a separate distinct `Jsonify::Builder` instance from the `json` variable in the main template.
183 |
184 | > Wishlist: Figure out if a the `json` instance can be passed to the Jsonify
185 | > partial to make things easier.
186 |
187 | The partial returns:
188 |
189 | ```json
190 | "[3,4]"
191 | ```
192 |
193 | Now, combining our index and partial we get:
194 |
195 | ```json
196 | "[1,[3,4]]"
197 | ```
198 |
199 | ##### Other partials
200 |
201 | You can also use output from non-Jsonify templates (e.g. erb); just remember that the output from a template is always a string and that you have to tell the builder how to include the result of the partial.
202 |
203 | Given an ERB partial:
204 |
205 | ```erb
206 |
207 | <%= Date.today %>
208 | ```
209 |
210 | and you use it in a Jsonify template:
211 |
212 | ```ruby
213 | json << 1
214 | json << { :date => (render :partial => 'today') }
215 | ```
216 |
217 | You get:
218 |
219 | ```json
220 | [1,{"date":"2011-07-30"}]
221 | ```
222 |
223 | ### Tilt Integration
224 |
225 | Jsonify includes support for [Tilt](http://github.com/rtomayko/tilt). This allow you to create views that use Jsonify with any framework that supports Tilt. Here's an example of a simple [Sinatra](http://sinatrarb.com) application that leverages Jsonify's Tilt integration:
226 |
227 | ```ruby
228 | require 'bundler/setup'
229 | require 'sinatra'
230 |
231 | require 'jsonify'
232 | require 'jsonify/tilt'
233 |
234 | helpers do
235 | def jsonify(*args)
236 | render(:jsonify, *args)
237 | end
238 | end
239 |
240 | get '/' do
241 | jsonify :index
242 | end
243 | ```
244 |
245 | And the corresponding template in `views\index.jsonify`
246 |
247 | ```json
248 | json.hello :frank
249 | ```
250 |
251 | ### Usage Patterns
252 |
253 | #### Background
254 |
255 | As mentioned before, Jsonify is designed to support construction of valid JSON and is entirely based on the [JSON specification](http://json.org).
256 |
257 | JSON is built on two fundamental structures:
258 |
259 | * __object__: a collection of name-value pairs -- in Jsonify this is a `JsonObject`
260 | * __array__: an ordered list of values -- in Jsonify this is a `JsonArray`
261 |
262 | Jsonify adheres to the JSON specification and provides explicit support
263 | for working with these primary structures. A JSON string
264 | must be one of these structures and Jsonify ensures that this condition is met.
265 |
266 | #### Creating JSON Objects
267 |
268 | A JSON object, sometimes referred to as an ___object literal___, is a common structure familiar to most developers. It's analogous to the nested element structure common in XML. The [JSON RFC](http://www.ietf.org/rfc/rfc4627.txt) states that "the names within an object SHOULD be unique". Jsonify enforces this recommendation by backing the JsonObject with a `Hash`; an object that must have unique keys and the last one in, wins.
269 |
270 | ```ruby
271 | json = Jsonify::Builder.new
272 | json.person do # start a new JsonObject where the key is 'foo'
273 | json.name 'George Burdell' # add a pair to this object
274 | json.skills ['engineering','bombing'] # adds a pair with an array value
275 | json.name 'George P. Burdell'
276 | end
277 | ```
278 |
279 | ```json
280 | {
281 | "person": {
282 | "name": "George P. Burdell",
283 | "skills": [
284 | "engineering",
285 | "bombing"
286 | ]
287 | }
288 | }
289 | ```
290 |
291 | It's perfectly legitimate for a JSON representation to simply be a collection
292 | of name-value pairs without a ___root___ element. Jsonify supports this by allowing you to specify the pairs that make up the object.
293 |
294 | ```ruby
295 | json = Jsonify::Builder.new
296 | json.location 'Library Coffeehouse'
297 | json.neighborhood 'Brookhaven'
298 | ```
299 |
300 | ```json
301 | {
302 | "location": "Library Coffeehouse",
303 | "neighborhood": "Brookhaven"
304 | }
305 | ```
306 |
307 | If the ___name___ you want contains whitespace or other characters not allowed in a Ruby method name, use `tag!`.
308 |
309 | ```ruby
310 | json.tag!("my location", 'Library Coffeehouse')
311 | json.neighborhood 'Brookhaven'
312 | ```
313 |
314 | ```json
315 | {
316 | "my location": "Library Coffeehouse",
317 | "neighborhood": "Brookhaven"
318 | }
319 | ```
320 |
321 | Jsonify also supports a hash-style interface for creating objects:
322 |
323 | ```ruby
324 | json = Jsonify::Builder.new
325 |
326 | json[:foo] = :bar
327 | json[:go] = :far
328 | ```
329 |
330 | ```json
331 | {
332 | "foo": "bar",
333 | "go": "far"
334 | }
335 | ```
336 |
337 | Use the hash-style within a block as well:
338 |
339 | ```ruby
340 | json.homer do
341 | json[:beer] = "Duffs"
342 | json[:spouse] = "Marge"
343 | end
344 | ```
345 |
346 | ```json
347 | {
348 | "homer": {
349 | "beer": "Duffs",
350 | "spouse": "Marge"
351 | }
352 | }
353 | ```
354 |
355 | Use `store!` for a more method-based style:
356 |
357 | ```ruby
358 | json.store!(:foo, :bar)
359 | json.store!(:go, :far)
360 | ```
361 |
362 | #### Creating JSON Arrays
363 |
364 | A JSON array is an ordered list of JSON values. A JSON value can be a simple value, like a string or a number, or a supported JavaScript primitive like true, false, or null. A JSON value can also be a JSON object or another JSON array. Jsonify strives to make this kind of construction possible in a buider-style.
365 |
366 | Jsonify supports JSON array construction through two approaches: `method_missing` and `append!`.
367 |
368 | ##### Use method_missing
369 |
370 | Pass an array and a block to `method_missing` (or `tag!`), and Jsonify will create a JSON array. It will then iterate over the array and call the block for each item in the array. Use the `json` object within the block to add items to the JSON array.
371 |
372 | That JSON array is then set as the value of the name-value pair, where the name comes from the method name (for `method_missing`)
373 | or symbol (for `tag!`).
374 |
375 | This construct creates a JSON pair and a JSON array as the value of the pair.
376 |
377 | ```ruby
378 | Jsonify::Builder.pretty do |json|
379 | json.letters('a'..'c') do |letter|
380 | json << letter.upcase
381 | end
382 | end
383 | ```
384 |
385 | ```json
386 | {
387 | "letters": [
388 | "A",
389 | "B",
390 | "C"
391 | ]
392 | }
393 | ```
394 |
395 | Another way to handle this particular example is to get rid of the block entirely.
396 | Simply pass the array directly:
397 |
398 | ```ruby
399 | json.letters ('a'..'c').map(&:upcase)
400 | ```
401 |
402 | ##### Use append!
403 |
404 | Use this method and the builder will assume you are adding values to a JSON array without the surrounding object.
405 |
406 | * Use `append!` for multiple values
407 | * Use `<<` for a single value
408 |
409 | Multiple values:
410 |
411 | ```ruby
412 | json = Jsonify::Builder.new
413 | json.append! 'a'.upcase, 'b'.upcase, 'c'.upcase
414 | ```
415 |
416 | ```json
417 | [
418 | "A",
419 | "B",
420 | "C"
421 | ]
422 | ```
423 | or more idiomatically:
424 |
425 | ```ruby
426 | json.append! *('a'..'c').map(&:upcase)
427 | ```
428 |
429 | Single values:
430 |
431 | ```ruby
432 | json << 'a'.upcase
433 | json << 'b'.upcase
434 | json << 'c'.upcase
435 | ```
436 |
437 | Standard iteration works here:
438 |
439 | ```ruby
440 | json = Jsonify::Builder.new
441 | ('a'..'c').each do |letter|
442 | json << letter.upcase
443 | end
444 | ```
445 |
446 | #### Mixing JSON Arrays and Objects
447 |
448 | You can readily mix JSON arrays and objects and the Jsonify builder will do
449 | its best to keep things straight.
450 |
451 | Start with an array and add an object:
452 |
453 | ```ruby
454 | json = Jsonify::Builder.new
455 | json.append! 1,2,3
456 | json.say "go, cat go"
457 | ```
458 |
459 | ```json
460 | [1,2,3,{"say":"go, cat go"}]
461 | ```
462 |
463 | The builder sees the name-value pair being added to an array and converts it to a JSON object.
464 |
465 | Now, start with an object and add an array:
466 |
467 | ```ruby
468 | json.foo 'bar'
469 | json.go 'far'
470 | json << 'baz'
471 | ```
472 |
473 | ```json
474 | {"foo":"bar","go":"far","baz":null}
475 | ```
476 |
477 | The builder now sees the single item (`baz`) and turns it into a name-value pair with a `null` value.
478 |
479 | ## Documentation
480 |
481 | [Yard Docs](http://rubydoc.info/github/bsiggelkow/jsonify/master/frames)
482 |
483 |
484 |
485 | ## Related Projects
486 |
487 | - [Argonaut](https://github.com/jbr/argonaut)
488 | - [jbuilder](https://github.com/rails/jbuilder)
489 | - [JSON Builder](https://github.com/dewski/json_builder)
490 | - [RABL](https://github.com/nesquena/rabl)
491 | - [Representative](https://github.com/mdub/representative)
492 | - [Tokamak](https://github.com/abril/tokamak)
493 |
494 | ## License
495 |
496 | This project is released under the MIT license.
497 |
498 | ## Authors
499 |
500 | * [Bill Siggelkow](https://github.com/bsiggelkow)
501 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require 'rspec/core/rake_task'
3 |
4 | RSpec::Core::RakeTask.new('spec')
5 | task :default => :spec
--------------------------------------------------------------------------------
/jsonify.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path("../lib", __FILE__)
3 | require "jsonify/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "jsonify"
7 | s.version = Jsonify::VERSION
8 | s.authors = ["Bill Siggelkow"]
9 | s.email = ["bsiggelkow@me.com"]
10 | s.homepage = "http://github.com/bsiggelkow/jsonify"
11 | s.summary = %q{Turn Ruby objects into JSON}
12 | s.description = %q{Turn Ruby objects into JSON -- correctly!}
13 |
14 | s.rubyforge_project = s.name
15 |
16 | s.files = `git ls-files`.split("\n")
17 | s.test_files = `git ls-files -- {test,spec,performance}/*`.split("\n")
18 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19 | s.require_paths = ["lib"]
20 |
21 | s.add_runtime_dependency 'multi_json', '~>1.3'
22 |
23 | s.add_development_dependency 'json' unless RUBY_VERSION =~ /^1.9/
24 | s.add_development_dependency 'bundler'
25 | s.add_development_dependency 'tilt', '~>1.3.2'
26 | s.add_development_dependency 'rake'
27 | s.add_development_dependency 'rspec'
28 | s.add_development_dependency 'yard'
29 | s.add_development_dependency 'rdiscount'
30 | end
31 |
--------------------------------------------------------------------------------
/lib/jsonify.rb:
--------------------------------------------------------------------------------
1 | require 'multi_json'
2 | require 'jsonify/blank_slate'
3 | require 'jsonify/version'
4 | require 'jsonify/json_value'
5 | require 'jsonify/generate'
6 | require 'jsonify/builder'
--------------------------------------------------------------------------------
/lib/jsonify/blank_slate.rb:
--------------------------------------------------------------------------------
1 | ######################################################################
2 | # Jsonify::BlankSlate is based on Jim Weirich's BlankSlate.
3 | #
4 | # Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org).
5 | # All rights reserved.
6 | #
7 | # BlankSlate provides an abstract base class with no predefined
8 | # methods (except for \_\_send__ and \_\_id__).
9 | # BlankSlate is useful as a base class when writing classes that
10 | # depend upon method_missing (e.g. dynamic proxies).
11 | #
12 | # This Jsonify implementation of BlankSlate is identical; with the
13 | # exception that it does not include the Kernel, Module, and Object
14 | # patches.
15 | #
16 | module Jsonify
17 | class BlankSlate
18 | class << self
19 |
20 | # Hide the method named +name+ in the BlankSlate class. Don't
21 | # hide +instance_eval+ or any method beginning with "__".
22 | def hide(name)
23 | if instance_methods.include?(name.to_s) and
24 | name !~ /^(__|instance_eval)/
25 | @hidden_methods ||= {}
26 | @hidden_methods[name.to_sym] = instance_method(name)
27 | undef_method name
28 | end
29 | end
30 |
31 | def find_hidden_method(name)
32 | @hidden_methods ||= {}
33 | @hidden_methods[name] || superclass.find_hidden_method(name)
34 | end
35 |
36 | # Redefine a previously hidden method so that it may be called on a blank
37 | # slate object.
38 | def reveal(name)
39 | hidden_method = find_hidden_method(name)
40 | fail "Don't know how to reveal method '#{name}'" unless hidden_method
41 | define_method(name, hidden_method)
42 | end
43 | end
44 |
45 | instance_methods.each { |m| hide(m) }
46 | end
47 | end
--------------------------------------------------------------------------------
/lib/jsonify/builder.rb:
--------------------------------------------------------------------------------
1 | module Jsonify
2 | class Builder < BlankSlate
3 |
4 | class << self
5 |
6 | # Compiles the given block into a JSON string without having to instantiate a Builder.
7 | #
8 | # @option options [boolean] :verify Builder will verify that the compiled JSON string is parseable; this option does incur a performance penalty and generally should only be used in development
9 | # @option options [symbol] :format Format for the resultant JSON string;
10 | # `:pretty`, the JSON string will be output in a prettier format with new lines and indentation; this option does incur a performance penalty and generally should only be used in development
11 | # `:plain`, no formatting (compact one-line JSON -- best for production)
12 | #
13 | def compile( options={} )
14 | builder = self.new options
15 | yield builder
16 | builder.compile!
17 | end
18 |
19 | # Compiles the given block into a pretty JSON string without having to instantiate a Builder.
20 | def pretty(&block)
21 | compile( :format => :pretty, &block )
22 | end
23 |
24 | # Compiles the given block into a plain (e.g. no newlines and whitespace) JSON string without having to instantiate a Builder.
25 | def plain(&block)
26 | compile( :format => :plain, &block )
27 | end
28 |
29 | end
30 |
31 | # Initializes a new builder. The Jsonify::Builder works by keeping a stack of +JsonValue+s.
32 | #
33 | # @param [Hash] options the options to create with
34 | # @option options [boolean] :verify Builder will verify that the compiled JSON string is parseable; this option does incur a performance penalty and generally should only be used in development
35 | # @option options [symbol] :format Format for the resultant JSON string;
36 | # `:pretty`, the JSON string will be output in a prettier format with new lines and indentation; this option does incur a performance penalty and generally should only be used in development
37 | # `:plain`, no formatting (compact one-line JSON -- best for production)
38 | def initialize(options={})
39 | @verify = options[:verify].nil? ? false : options[:verify]
40 | @pretty = options[:format].to_s == 'pretty' ? true : false
41 | reset!
42 | end
43 |
44 | # Clears the builder data
45 | def reset!
46 | @level = 0
47 | @stack = []
48 | end
49 |
50 | # Adds a new JsonPair to the builder. Use this method if the pair "key" has spaces or other characters that prohibit creation via method_missing.
51 | #
52 | # @param sym [String] the key for the pair
53 | # @param *args [arguments] If a block is passed, the first argument will be iterated over and the subsequent result will be added to a JSON array; otherwise, the arguments set value for the `JsonPair`
54 | # @param &block a code block the result of which will be used to populate the value for the JSON pair
55 | def tag!(sym, args=nil, &block)
56 | method_missing(sym, *args, &block)
57 | end
58 |
59 | # Adds a new JsonPair for each attribute in attrs by taking attr as key and value of that attribute in object.
60 | #
61 | # @param object [Object] Object to take values from
62 | # @param *attrs [Array] Array of attributes for JsonPair keys
63 | def attributes!(object, *attrs)
64 | attrs.each do |attr|
65 | method_missing attr, object.send(attr)
66 | end
67 | end
68 |
69 | # Compiles the JSON objects into a string representation.
70 | # If initialized with +:verify => true+, the compiled result will be verified by attempting to re-parse it using +MultiJson.load+.
71 | # If initialized with +:format => :pretty+, the compiled result will be parsed and encoded via +MultiJson.dump(, :pretty => true)+
72 | # This method can be called without any side effects. You can call +compile!+ at any time, and multiple times if desired.
73 | #
74 | # @raise [TypeError] only if +:verify+ is set to true
75 | # @raise [JSON::ParseError] only if +:verify+ is set to true
76 | def compile!
77 | result = (@stack[0] || {}).encode_as_json
78 | MultiJson.load(result) if @verify
79 | result = MultiJson.dump(MultiJson.load(result), :pretty => true) if @pretty
80 | result
81 | end
82 |
83 | # Stores the key and value into a JSON object
84 | # @param key the key for the pair
85 | # @param value the value for the pair
86 | # @return self to allow for chaining
87 | def store!(key, value=nil)
88 | (@stack[@level] ||= JsonObject.new).add(key,value)
89 | self
90 | end
91 |
92 | alias_method :[]=, :store!
93 |
94 | # Append -- pushes the given object on the end of a JsonArray.
95 | def <<(val)
96 | __array
97 | @stack[@level].add val
98 | self
99 | end
100 |
101 | # Append -- pushes the given variable list objects on to the end of the JsonArray
102 | def append!(*args)
103 | __array
104 | args.each do |arg|
105 | @stack[@level].add arg
106 | end
107 | self
108 | end
109 |
110 | # Creates array of json objects in current element from array passed to this method.
111 | # Accepts block which yields each array element.
112 | #
113 | # @example Create array in root JSON element
114 | # json.array!(@links) do |link|
115 | # json.rel link.first
116 | # json.href link.last
117 | # end
118 | #
119 | # @example compiles to something like ...
120 | # [
121 | # {
122 | # "rel": "self",
123 | # "href": "http://example.com/people/123"
124 | # },
125 | # {
126 | # "rel": "school",
127 | # "href": "http://gatech.edu"
128 | # }
129 | # ]
130 | #
131 | def array!(args)
132 | __array
133 | args.each do |arg|
134 | @level += 1
135 | yield arg
136 | @level -= 1
137 |
138 | value = @stack.pop
139 |
140 | # If the object created was an array with a single value
141 | # assume that just the value should be added
142 | if (JsonArray === value && value.values.length <= 1)
143 | value = value.values.first
144 | end
145 |
146 | @stack[@level].add value
147 | end
148 | end
149 |
150 |
151 | # Adds a new JsonPair to the builder where the key of the pair is set to the method name
152 | # (`sym`).
153 | # When passed a block, the value of the pair is set to the result of that
154 | # block; otherwise, the value is set to the argument(s) (`args`).
155 | #
156 | # @example Create an object literal
157 | # json.person do
158 | # json.first_name @person.given_name
159 | # json.last_name @person.surname
160 | # end
161 | #
162 | # @example compiles to something like ...
163 | # "person": {
164 | # "first_name": "George",
165 | # "last_name": "Burdell"
166 | # }
167 | #
168 | # If a block is given and an argument is passed, the argument it is assumed to be an
169 | # Array (more specifically, an object that responds to `each`).
170 | # The argument is iterated over and each item is yielded to the block.
171 | # The result of the block becomes an array item of the JsonArray.
172 | #
173 | # @example Map an of array of links to an array of JSON objects
174 | # json.links(@links) do |link|
175 | # json.rel link.first
176 | # json.href link.last
177 | # end
178 | #
179 | # @example compiles to something like ...
180 | # "links": [
181 | # {
182 | # "rel": "self",
183 | # "href": "http://example.com/people/123"
184 | # },
185 | # {
186 | # "rel": "school",
187 | # "href": "http://gatech.edu"
188 | # }
189 | # ]
190 | #
191 | # @param *args [Array] iterates over the given array yielding each array item to the block; the result of which is added to a JsonArray
192 | def method_missing(sym, args=nil, &block)
193 |
194 | # When no block given, simply add the symbol and arg as key - value for a JsonPair to current
195 | return __store( sym, args ) unless block
196 |
197 | # In a block; create a JSON pair (with no value) and add it to the current object
198 | pair = Generate.pair_value(sym)
199 | __store pair
200 |
201 | # Now process the block
202 | @level += 1
203 |
204 | if args.nil?
205 | block.call
206 | else
207 | array!(args, &block)
208 | end
209 |
210 | # Set the value on the pair to the object at the top of the stack
211 | pair.value = @stack[@level]
212 |
213 | # Pop current off the top of the stack; we are done with it at this point
214 | @stack.pop
215 |
216 | @level -= 1
217 | end
218 |
219 | # Ingest a full JSON representation (either an oject or array)
220 | # into the builder. The value is parsed, objectified, and added to the
221 | # current value at the top of the stack.
222 | #
223 | # @param [String] json_string a full JSON string (e.g. from a rendered partial)
224 | def ingest!(json_string)
225 | return if json_string.empty?
226 | res = Jsonify::Generate.value(MultiJson.load(json_string))
227 | current = @stack[@level]
228 | if current.nil?
229 | @stack[@level] = res
230 | elsif JsonObject === current
231 | if JsonObject === res
232 | @stack[@level].merge res
233 | else
234 | raise ArgumentError.new("Cannot add JSON array to JSON Object")
235 | end
236 | else # current is JsonArray
237 | @stack[@level].add res
238 | end
239 | end
240 |
241 | private
242 |
243 | # BlankSlate requires the __ names
244 |
245 | def __store(key,value=nil)
246 | pair = (JsonPair === key ? key : JsonPair.new(key, value))
247 | (@stack[@level] ||= JsonObject.new).add(pair)
248 | end
249 |
250 | def __array
251 | @stack[@level] ||= JsonArray.new
252 | end
253 |
254 | end
255 | end
256 |
--------------------------------------------------------------------------------
/lib/jsonify/generate.rb:
--------------------------------------------------------------------------------
1 | module Jsonify
2 |
3 | # Provides a set of functions for creating JsonValues from Ruby objects.
4 | module Generate
5 |
6 | class << self
7 |
8 | # Coerces the given value into a JsonValue (or subclass), String, or Number.
9 | #
10 | # The coercion rules are based on the type (class) of the value as follows:
11 | # - +JsonValue+ => no coercion
12 | # - +String+ => no coercion
13 | # - +Numeric+ => no coercion
14 | # - +TrueClass+ => JsonTrue ( true )
15 | # - +FalseClass+=> JsonFalse ( false )
16 | # - +NilClass+ => JsonNull ( null )
17 | # - +Array+ => JsonArray ( [1,2,3] )
18 | # - +Hash+ => JsonObject ( {"\a":1,\"b\":2}
)
19 | # - +else+ => #to_s
20 | #
21 | # @param val value to coerce into a JsonValue.
22 | def value(val)
23 | case val
24 | when JsonValue, String, Numeric; val
25 | when TrueClass; @json_true ||= JsonTrue.new
26 | when FalseClass; @json_false ||= JsonFalse.new
27 | when NilClass; @json_null ||= JsonNull.new
28 | when Array; array_value val
29 | when Hash; object_value val
30 | else val.to_s
31 | end
32 | end
33 |
34 | def pair_value(key,val=nil)
35 | JsonPair.new(key,value(val))
36 | end
37 |
38 | def object_value(hash)
39 | json_object = JsonObject.new
40 | hash.each { |key,val| json_object.add( pair_value(key, val) ) }
41 | json_object
42 | end
43 |
44 | def array_value(vals)
45 | JsonArray.new(Array(vals).map{ |v| value v })
46 | end
47 |
48 | end
49 |
50 | end
51 |
52 | end
--------------------------------------------------------------------------------
/lib/jsonify/json_value.rb:
--------------------------------------------------------------------------------
1 | class Object
2 | def encode_as_json
3 | MultiJson.dump self
4 | end
5 | end
6 |
7 | module Jsonify
8 | class JsonValue
9 | attr_accessor :values
10 |
11 | def initialize(values=nil)
12 | @values = values || []
13 | end
14 |
15 | def encode_as_json
16 | wrap values.map {|v| v.encode_as_json}.join(',')
17 | end
18 |
19 | def add(jsonValue)
20 | values << Generate.value(jsonValue)
21 | end
22 |
23 | end
24 |
25 | class JsonObject < JsonValue
26 | def initialize(values=nil)
27 | @values = values || {}
28 | end
29 |
30 | def wrap(joined_values)
31 | "{#{joined_values}}"
32 | end
33 |
34 | def values
35 | @values.values
36 | end
37 |
38 | def add(key, val=nil)
39 | pair = ( JsonPair === key ? key : JsonPair.new(key, val) )
40 | @values.store(pair.key, pair)
41 | end
42 |
43 | def merge(json_object)
44 | json_object.values.each do |pair|
45 | @values.store(pair.key, pair)
46 | end
47 | end
48 |
49 | alias_method :<<, :add
50 |
51 | end
52 |
53 | class JsonArray < JsonValue
54 |
55 | def wrap(joined_values)
56 | "[#{joined_values}]"
57 | end
58 |
59 | def add(value)
60 | if JsonPair === value # wrap JsonPair in a JsonObject
61 | object = JsonObject.new
62 | object.add value
63 | value = object
64 | end
65 | super(value)
66 | end
67 |
68 | alias_method :<<, :add
69 |
70 | end
71 |
72 | class JsonPair < JsonValue
73 | attr_accessor :key, :value
74 | def initialize(key, value=nil)
75 | @key = key.to_s
76 | @value = Generate.value(value)
77 | end
78 | def encode_as_json
79 | %Q{#{key.encode_as_json}:#{value.encode_as_json}}
80 | end
81 | end
82 |
83 | class JsonTrue < JsonValue
84 | def encode_as_json
85 | 'true'
86 | end
87 | end
88 |
89 | class JsonFalse < JsonValue
90 | def encode_as_json
91 | 'false'
92 | end
93 | end
94 |
95 | class JsonNull < JsonValue
96 | def encode_as_json
97 | 'null'
98 | end
99 | end
100 |
101 | end
--------------------------------------------------------------------------------
/lib/jsonify/template.rb:
--------------------------------------------------------------------------------
1 |
2 | module Jsonify
3 |
4 | class Template < Tilt::Template
5 | self.default_mime_type = 'application/json'
6 |
7 | def self.engine_initialized?
8 | defined? ::Jsonify
9 | end
10 |
11 | def initialize_engine
12 | require_template_library 'jsonify'
13 | end
14 |
15 | def prepare; end
16 |
17 | def evaluate(scope, locals, &block)
18 | return super(scope, locals, &block) if data.respond_to?(:to_str)
19 | json = ::Jsonify::Builder.new
20 | data.call(json)
21 | json.compile!
22 | end
23 |
24 | def precompiled_preamble(locals)
25 | return super if locals.include? :json
26 | "json = ::Jsonify::Builder.new\n#{super}"
27 | end
28 |
29 | def precompiled_postamble(locals)
30 | "json.compile!"
31 | end
32 |
33 | def precompiled_template(locals)
34 | data.to_str
35 | end
36 | end
37 |
38 | end
39 |
--------------------------------------------------------------------------------
/lib/jsonify/tilt.rb:
--------------------------------------------------------------------------------
1 | require 'tilt'
2 | require 'tilt/template'
3 | require 'jsonify/template'
4 |
5 | Tilt.register Jsonify::Template, 'jsonify'
--------------------------------------------------------------------------------
/lib/jsonify/version.rb:
--------------------------------------------------------------------------------
1 | module Jsonify
2 | VERSION = "0.4.1"
3 | end
4 |
--------------------------------------------------------------------------------
/performance/profile.rb:
--------------------------------------------------------------------------------
1 | require 'bundler'
2 | require 'bundler/setup'
3 | require 'jsonify'
4 | require 'ruby-prof'
5 |
6 | result = RubyProf.profile do
7 | 1.times do
8 | json=Jsonify::Builder.new
9 | json.hello 'world'
10 | json.compile!
11 | end
12 | end
13 |
14 | # Print a flat profile to text
15 | printer = RubyProf::FlatPrinter.new(result)
16 | printer.print(STDOUT)
--------------------------------------------------------------------------------
/performance/speed.rb:
--------------------------------------------------------------------------------
1 | require 'bundler'
2 | require 'bundler/setup'
3 | require 'jsonify'
4 | require 'benchmark'
5 |
6 | class Speed
7 | def self.test
8 | Benchmark.bm do |b|
9 | b.report('Jsonify') do
10 | 15_000.times {
11 | j = Jsonify::Builder.new
12 | j.name "Garrett Bjerkhoel"
13 | j.birthday Time.local(1991, 9, 14)
14 | j.street do
15 | j.address "1143 1st Ave"
16 | j.address2 "Apt 200"
17 | j.city "New York"
18 | j.state "New York"
19 | j.zip 10065
20 | end
21 | j.skills do
22 | j.ruby true
23 | j.asp false
24 | j.php true
25 | j.mysql true
26 | j.mongodb true
27 | j.haproxy true
28 | j.marathon false
29 | end
30 | j.single_skills ['ruby', 'php', 'mysql', 'mongodb', 'haproxy']
31 | j.booleans [true, true, false, nil]
32 | j.compile!
33 | }
34 | end
35 | end
36 | end
37 | end
38 |
39 | Speed.test
--------------------------------------------------------------------------------
/spec/builder_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Jsonify::Builder do
4 |
5 | let(:json) { Jsonify::Builder.new }
6 |
7 | describe 'class methods' do
8 | it '#compile should compile' do
9 | Jsonify::Builder.compile do |j|
10 | j.foo 'bar'
11 | end.should == '{"foo":"bar"}'
12 | end
13 | it '#pretty should be pretty' do
14 | pretty_results = < true)
59 | json.stack << FooBar.new
60 | lambda{ json.compile! }.should raise_error(MultiJson::DecodeError)
61 | end
62 | end
63 | describe 'unicode characters' do
64 | it 'should properly encode' do
65 | json = Jsonify::Builder.new(:verify => true)
66 | json.foo 'bar'.concat(16)
67 | lambda { json.compile! }.should_not raise_error
68 | end
69 | end
70 | describe "pretty printing" do
71 | it "should not be pretty by default" do
72 | json.foo do
73 | json.bar 'baz'
74 | end
75 | non_pretty_results = '{"foo":{"bar":"baz"}}'
76 | json.compile!.should == non_pretty_results
77 | end
78 | it "should be pretty when asked for" do
79 | json = Jsonify::Builder.new(:format => :pretty)
80 | json.foo do
81 | json.bar 'baz'
82 | end
83 | pretty_results = < :bar}
130 | json << {:go => :far}
131 | json.compile!.should == '[{"foo":"bar"},{"go":"far"}]'
132 | end
133 | end
134 |
135 | describe 'objects' do
136 | it 'simple object should work' do
137 | json.foo :bar
138 | json.go :far
139 | expected = '{"foo":"bar","go":"far"}'
140 | MultiJson.load(json.compile!).should == MultiJson.load(expected)
141 | end
142 | it 'should handle arrays' do
143 | json[1] = [2, 3]
144 | json[4] = 5
145 | MultiJson.load(json.compile!).should == MultiJson.load('{"1":[2,3],"4":5}')
146 | end
147 | end
148 |
149 | describe "attributes!" do
150 | it "should allow create object with attributes of another object" do
151 | object = stub(:id => 1, :name => 'foo')
152 | json.attributes!(object, :id, :name)
153 | MultiJson.load(json.compile!).should == {'id' => 1, 'name' => 'foo'}
154 | end
155 | end
156 |
157 | describe 'using blocks' do
158 |
159 | it 'should allow names with spaces using tag!' do
160 | json.tag!("foo foo") do
161 | json.tag!("bar bar") do
162 | json.tag!('buzz buzz','goo goo')
163 | end
164 | end
165 | expected = '{"foo foo":{"bar bar":{"buzz buzz":"goo goo"}}}'
166 | MultiJson.load(json.compile!).should == MultiJson.load(expected)
167 | end
168 |
169 | it 'complex hash' do
170 | json.foo do
171 | json.bar do
172 | json.baz 'goo'
173 | end
174 | end
175 | json.compile!.should == '{"foo":{"bar":{"baz":"goo"}}}'
176 | end
177 |
178 | it 'simple hash' do
179 | json.foo do
180 | json.baz :goo
181 | end
182 | json.compile!.should == '{"foo":{"baz":"goo"}}'
183 | end
184 |
185 | it 'hash with array' do
186 | json.foo do
187 | json << 1
188 | json << 2
189 | end
190 | json.compile!.should == '{"foo":[1,2]}'
191 | end
192 |
193 | it 'hash with array by iteration' do
194 | ary = [1,2,3]
195 | json.foo do
196 | ary.each do |n|
197 | json << (n * 2)
198 | end
199 | end
200 | json.compile!.should == '{"foo":[2,4,6]}'
201 | end
202 |
203 | it 'simple array with object' do
204 | json << 1
205 | json << {:foo => :bar}
206 | json.compile!.should == '[1,{"foo":"bar"}]'
207 | end
208 |
209 | it 'simple array with object via method_missing' do
210 | json << 1
211 | json << 2
212 | json.foo :bar
213 | json.compile!.should == "[1,2,{\"foo\":\"bar\"}]"
214 | end
215 |
216 | it 'complex hash with array' do
217 | json.foo do
218 | json.bar do
219 | json.baz 'goo'
220 | json.years do
221 | json << 2011
222 | json << 2012
223 | end
224 | end
225 | end
226 | expected = "{\"foo\":{\"bar\":{\"baz\":\"goo\",\"years\":[2011,2012]}}}"
227 | MultiJson.load(json.compile!).should == MultiJson.load(expected)
228 | end
229 | end
230 |
231 | describe 'without blocks' do
232 |
233 | describe 'complex array' do
234 | it 'should work' do
235 | json.bar [1,2,{:foo => 'goo'}]
236 | expected = "{\"bar\":[1,2,{\"foo\":\"goo\"}]}"
237 | MultiJson.load(json.compile!).should == MultiJson.load(expected)
238 | end
239 | end
240 |
241 | describe 'object with null' do
242 | it 'should handle missing argument' do
243 | json.foo
244 | json.compile!.should == '{"foo":null}'
245 | end
246 | end
247 |
248 | end
249 |
250 | describe 'super complex example' do
251 | let(:links) {
252 | link_class = Struct.new(:url,:type)
253 | [
254 | link_class.new('example.com', 'self'),
255 | link_class.new('foo.com', 'parent')
256 | ]
257 | }
258 | it 'should work using arrays' do
259 | json.result do
260 | json.person do
261 | json.fname 'George'
262 | json.lname 'Burdell'
263 | end
264 | json.links(links) do |link|
265 | json.href link.url
266 | json.rel link.type
267 | end
268 | end
269 | expected = "{\"result\":{\"person\":{\"fname\":\"George\",\"lname\":\"Burdell\"},\"links\":[{\"href\":\"example.com\",\"rel\":\"self\"},{\"href\":\"foo.com\",\"rel\":\"parent\"}]}}"
270 | MultiJson.load(json.compile!).should == MultiJson.load(expected)
271 | end
272 | end
273 |
274 | describe 'ingest!' do
275 | context 'a json object' do
276 | let(:json_string) { '{"my girl":"Friday","my daughter":"Wednesday"}' }
277 | context 'into' do
278 | it 'nothing -- should replace it' do
279 | json.ingest! json_string
280 | MultiJson.load(json.compile!).should == MultiJson.load(json_string)
281 | end
282 | it 'json object -- should merge' do
283 | json["my boy"] = "Monday"
284 | json["my girl"] = "Sunday"
285 | json.ingest! json_string
286 | expected = '{"my boy":"Monday","my girl":"Friday","my daughter":"Wednesday"}'
287 | MultiJson.load(json.compile!).should == MultiJson.load(expected)
288 | end
289 | it 'json array -- should add' do
290 | json << 1 << 2
291 | json.ingest! json_string
292 | expected = '[1,2,{"my girl":"Friday","my daughter":"Wednesday"}]'
293 | MultiJson.load(json.compile!).should == MultiJson.load(expected)
294 | end
295 | end
296 | end
297 | context 'a json array' do
298 | let(:json_string) { '[1,2,3]' }
299 | context 'into' do
300 | it 'nothing -- should replace it' do
301 | json.ingest! json_string
302 | MultiJson.load(json.compile!).should == MultiJson.load(json_string)
303 | end
304 | it 'json object -- should raise error' do
305 | json["my boy"] = "Monday"
306 | json["my girl"] = "Sunday"
307 | lambda{ json.ingest! json_string }.should raise_error( ArgumentError )
308 | end
309 | it 'json array -- should add' do
310 | json << 1 << 2
311 | json.ingest! json_string
312 | expected = '[1,2,[1,2,3]]'
313 | MultiJson.load(json.compile!).should == MultiJson.load(expected)
314 | end
315 | end
316 | end
317 | end
318 |
319 | describe 'with new array style' do
320 | it 'should work' do
321 | results =[
322 | {:id => 1, :kids => [{:id => 'a'},{:id => 'b'}]},
323 | {:id => 2, :kids => [{:id => 'c'},{:id => 'd'}]},
324 | ]
325 |
326 | json.results(results) do |result|
327 | json.id result[:id]
328 | json.children(result[:kids]) do |kid|
329 | json.id kid[:id]
330 | end
331 | end
332 |
333 | expected = '{"results":[{"id":1,"children":[{"id":"a"},{"id":"b"}]},{"id":2,"children":[{"id":"c"},{"id":"d"}]}]}'
334 | MultiJson.load(json.compile!).should == MultiJson.load(expected)
335 | end
336 | it 'simple append' do
337 | json.letters('a'..'c') do |letter|
338 | json << letter.upcase
339 | end
340 | expected = '{"letters":["A","B","C"]}'
341 | MultiJson.load(json.compile!).should == MultiJson.load(expected)
342 | end
343 |
344 | end
345 |
346 | describe 'array!' do
347 | it 'allow creating array from root' do
348 | json.array!([1, 2, 3]) do |number|
349 | json.id number
350 | json.times2 number * 2
351 | end
352 |
353 | MultiJson.load(json.compile!).should == [
354 | {'id' => 1, 'times2' => 2},
355 | {'id' => 2, 'times2' => 4},
356 | {'id' => 3, 'times2' => 6},
357 | ]
358 | end
359 | end
360 | end
361 |
--------------------------------------------------------------------------------
/spec/generate_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Jsonify::Generate do
4 | let(:links) do
5 | { :links =>
6 | [
7 | {:rel => 'foo', :href => 'goo'},
8 | {:rel => 'bar', :href => 'baz'}
9 | ]
10 | }
11 | end
12 | it 'should build json' do
13 | json = Jsonify::Generate
14 | result = json.value links
15 | expected = '{"links":[{"rel":"foo","href":"goo"},{"rel":"bar","href":"baz"}]}'
16 | MultiJson.load(result.encode_as_json).should == MultiJson.load(expected)
17 | end
18 |
19 | describe 'complex example' do
20 | let(:jsonifier) { Jsonify::Generate }
21 |
22 | it 'should work' do
23 | json = jsonifier.object_value(
24 | {"links" =>
25 | jsonifier.array_value([
26 | jsonifier.object_value( {"rel" => "foo", "href" => "goo"} ),
27 | jsonifier.object_value( {"rel" => "bar", "href" => "baz"} )
28 | ])
29 | }
30 | )
31 | expected = "{\"links\":[{\"rel\":\"foo\",\"href\":\"goo\"},{\"rel\":\"bar\",\"href\":\"baz\"}]}"
32 | MultiJson.load(json.encode_as_json).should == MultiJson.load(expected)
33 | end
34 | end
35 | end
--------------------------------------------------------------------------------
/spec/hello_world.jsonify:
--------------------------------------------------------------------------------
1 | json.hello 'world'
--------------------------------------------------------------------------------
/spec/json_value_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Jsonify::JsonValue do
4 |
5 | describe Jsonify::JsonPair do
6 | let(:pair) { Jsonify::JsonPair.new('key','value') }
7 | it 'should be constructed of a key and value' do
8 | pair.key.should == 'key'
9 | end
10 | it 'should evaluate to key:value' do
11 | pair.encode_as_json.should == "\"key\":\"value\""
12 | end
13 | end
14 |
15 | describe Jsonify::JsonTrue do
16 | it 'should have a value of true' do
17 | Jsonify::JsonTrue.new.encode_as_json.should == 'true'
18 | end
19 | end
20 |
21 | describe Jsonify::JsonFalse do
22 | it 'should have a value of false' do
23 | Jsonify::JsonFalse.new.encode_as_json.should == 'false'
24 | end
25 | end
26 |
27 | describe Jsonify::JsonNull do
28 | it 'should have a value of true' do
29 | Jsonify::JsonNull.new.encode_as_json.should == 'null'
30 | end
31 | end
32 |
33 | describe 'strings' do
34 | it 'should quote the value' do
35 | 'foo'.encode_as_json.should == "\"foo\""
36 | end
37 | it 'should encode unicode' do
38 | unicode = 'goober'.concat(16)
39 | unicode.encode_as_json.should == "\"goober\\u0010\""
40 | end
41 | end
42 |
43 |
44 | end
--------------------------------------------------------------------------------
/spec/jsonify_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | describe Jsonify do
3 | it('should be true') {true.should be_true}
4 | end
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'json'
2 | require 'benchmark'
3 | require 'bundler'
4 | require 'bundler/setup'
5 | require 'jsonify'
6 | require 'jsonify/tilt'
7 |
8 | RSpec.configure do |config|
9 | end
--------------------------------------------------------------------------------
/spec/template_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Jsonify::Template do
4 | it 'should be associated with .jsonify files' do
5 | template = Tilt.new('spec/hello_world.jsonify')
6 | template.should be_a_kind_of(Jsonify::Template)
7 | end
8 | it 'should render the template' do
9 | template = Tilt.new('spec/hello_world.jsonify')
10 | template.render.should == "{\"hello\":\"world\"}"
11 | end
12 | end
--------------------------------------------------------------------------------