├── .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 [![Build Status](https://secure.travis-ci.org/bsiggelkow/jsonify.png)](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 --------------------------------------------------------------------------------