jsonify-0.4.1/0000755000000000000000000000000012406721251011673 5ustar rootrootjsonify-0.4.1/performance/0000755000000000000000000000000012406721251014174 5ustar rootrootjsonify-0.4.1/performance/speed.rb0000644000000000000000000000160112406721251015617 0ustar rootrootrequire 'bundler' require 'bundler/setup' require 'jsonify' require 'benchmark' class Speed def self.test Benchmark.bm do |b| b.report('Jsonify') do 15_000.times { j = Jsonify::Builder.new j.name "Garrett Bjerkhoel" j.birthday Time.local(1991, 9, 14) j.street do j.address "1143 1st Ave" j.address2 "Apt 200" j.city "New York" j.state "New York" j.zip 10065 end j.skills do j.ruby true j.asp false j.php true j.mysql true j.mongodb true j.haproxy true j.marathon false end j.single_skills ['ruby', 'php', 'mysql', 'mongodb', 'haproxy'] j.booleans [true, true, false, nil] j.compile! } end end end end Speed.testjsonify-0.4.1/performance/profile.rb0000644000000000000000000000045512406721251016165 0ustar rootrootrequire 'bundler' require 'bundler/setup' require 'jsonify' require 'ruby-prof' result = RubyProf.profile do 1.times do json=Jsonify::Builder.new json.hello 'world' json.compile! end end # Print a flat profile to text printer = RubyProf::FlatPrinter.new(result) printer.print(STDOUT)jsonify-0.4.1/metadata.yml0000644000000000000000000000741212406721251014202 0ustar rootroot--- !ruby/object:Gem::Specification name: jsonify version: !ruby/object:Gem::Version version: 0.4.1 prerelease: platform: ruby authors: - Bill Siggelkow autorequire: bindir: bin cert_chain: [] date: 2012-09-12 00:00:00.000000000Z dependencies: - !ruby/object:Gem::Dependency name: multi_json requirement: &70302252507960 !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '1.3' type: :runtime prerelease: false version_requirements: *70302252507960 - !ruby/object:Gem::Dependency name: bundler requirement: &70302252506420 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: *70302252506420 - !ruby/object:Gem::Dependency name: tilt requirement: &70302252482580 !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: 1.3.2 type: :development prerelease: false version_requirements: *70302252482580 - !ruby/object:Gem::Dependency name: rake requirement: &70302252480320 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: *70302252480320 - !ruby/object:Gem::Dependency name: rspec requirement: &70302252475700 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: *70302252475700 - !ruby/object:Gem::Dependency name: yard requirement: &70302252423420 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: *70302252423420 - !ruby/object:Gem::Dependency name: rdiscount requirement: &70302252420620 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: *70302252420620 description: Turn Ruby objects into JSON -- correctly! email: - bsiggelkow@me.com executables: [] extensions: [] extra_rdoc_files: [] files: - .gitignore - .travis.yml - Gemfile - LICENSE - README.md - Rakefile - jsonify.gemspec - lib/jsonify.rb - lib/jsonify/blank_slate.rb - lib/jsonify/builder.rb - lib/jsonify/generate.rb - lib/jsonify/json_value.rb - lib/jsonify/template.rb - lib/jsonify/tilt.rb - lib/jsonify/version.rb - performance/profile.rb - performance/speed.rb - spec/builder_spec.rb - spec/generate_spec.rb - spec/hello_world.jsonify - spec/json_value_spec.rb - spec/jsonify_spec.rb - spec/spec_helper.rb - spec/template_spec.rb homepage: http://github.com/bsiggelkow/jsonify licenses: [] post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' segments: - 0 hash: -344377387242156613 required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' segments: - 0 hash: -344377387242156613 requirements: [] rubyforge_project: jsonify rubygems_version: 1.8.6 signing_key: specification_version: 3 summary: Turn Ruby objects into JSON test_files: - performance/profile.rb - performance/speed.rb - spec/builder_spec.rb - spec/generate_spec.rb - spec/hello_world.jsonify - spec/json_value_spec.rb - spec/jsonify_spec.rb - spec/spec_helper.rb - spec/template_spec.rb has_rdoc: jsonify-0.4.1/.travis.yml0000644000000000000000000000007612406721251014007 0ustar rootrootrvm: - 1.9.3 - 1.9.2 - 1.8.7 - ree - rbx - jruby jsonify-0.4.1/.gitignore0000644000000000000000000000005712406721251013665 0ustar rootroot*.gem .bundle Gemfile.lock pkg/* .yardoc/ doc/ jsonify-0.4.1/Gemfile0000644000000000000000000000012012406721251013157 0ustar rootrootsource "http://rubygems.org" # Gem dependencies are in jsonify.gemspec gemspec jsonify-0.4.1/Rakefile0000644000000000000000000000016412406721251013341 0ustar rootrootrequire 'bundler/gem_tasks' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new('spec') task :default => :specjsonify-0.4.1/LICENSE0000644000000000000000000000206212406721251012700 0ustar rootrootThe MIT License Copyright (c) 2011 Bill Siggelkow Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.jsonify-0.4.1/jsonify.gemspec0000644000000000000000000000207012406721251014720 0ustar rootroot# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "jsonify/version" Gem::Specification.new do |s| s.name = "jsonify" s.version = Jsonify::VERSION s.authors = ["Bill Siggelkow"] s.email = ["bsiggelkow@me.com"] s.homepage = "http://github.com/bsiggelkow/jsonify" s.summary = %q{Turn Ruby objects into JSON} s.description = %q{Turn Ruby objects into JSON -- correctly!} s.rubyforge_project = s.name s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,performance}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.add_runtime_dependency 'multi_json', '~>1.3' s.add_development_dependency 'json' unless RUBY_VERSION =~ /^1.9/ s.add_development_dependency 'bundler' s.add_development_dependency 'tilt', '~>1.3.2' s.add_development_dependency 'rake' s.add_development_dependency 'rspec' s.add_development_dependency 'yard' s.add_development_dependency 'rdiscount' end jsonify-0.4.1/README.md0000644000000000000000000003706212406721251013162 0ustar rootroot# Jsonify — a builder for JSON [![Build Status](https://secure.travis-ci.org/bsiggelkow/jsonify.png)](http://travis-ci.org/bsiggelkow/jsonify) [Jsonify](https://github.com/bsiggelkow/jsonify) is to JSON as [Builder](https://github.com/jimweirich/builder) is to XML. To use Jsonify for Rails templates, install [Jsonify-Rails](https://github.com/bsiggelkow/jsonify-rails). ## Goal Jsonify provides a ___builder___ style engine for creating correct JSON representations of Ruby objects. ## Motivation JSON and XML are without a doubt the most common representations used by RESTful applications. Jsonify was built around the notion that these representations belong in the ___view___ layer of the application. For XML representations, Rails makes this easy through its support of Builder templates, but, when it comes to JSON, there is no clear approach. For many applications, particularly those based on legacy database, its not uncommon to expose the data in more client-friendly representations than would be presented by the default Rails `to_json` method. Rails does provide control of the emitted via a custom implementation of `as_json`. Nevertheless, this forces the developer to place this code into the model when it more rightly belongs in the view. When someone asks "Where are the model representations defined?", I don't want to have to say "Well, look in the views folder for XML, but you have to look at the code in the model for the JSON format." There are a number of other libraries available that try to solve this problem. Some 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 when you consider 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 format or the other. But an even greater motivation for me was emulating the simplicity of [Builder](https://github.com/jimweirich/builder). I have not found a single framework for JSON that provides the simplicity and elegance of Builder. Jsonify is my attempt at remedying that situation. ## Installation `gem install jsonify` ## Usage In the examples that follow, the JSON output is usually shown "prettified". This is only for illustration purposes, as the default behavior for Jsonify is not to prettify the output. You can enable prettification by passing `:format => :pretty` to the Jsonify::Builder constructor; however, pretty printing is a relatively costly operation and should not be used in production (unless, of course, you explicitly want to show this format). The default format, `plain`, dictates no special formatting: the result will be rendered as a compact string without any newlines. ### Compatibility Warning Starting with version 0.2.0, the handling of arrays has changed to provide a more natural feel. As a consequence, however, code written using earlier versions of Jsonify may not work correctly. The example that follows demonstrates the changes you need to make. Previously, when arrays were processed, you had to put away the builder-style, and use more conventional Rubyisms. json.links(@links) do |link| {:rel => link.type, :href => link.url} end This difference was a frequent stumbling block with users and I wanted to remedy it. The interface for handling arrays is now consistent with the builder-style and should be less surprising to developers. The above snippet is now implemented as: json.links(@links) do |link| json.rel link.type json.href link.url end As always, all feedback is greatly appreciated. I want to know how this new style works out. ### Standalone # Create some objects that represent a person and associated hyperlinks @person = Struct.new(:first_name,:last_name).new('George','Burdell') Link = Struct.new(:type, :url) @links = [ Link.new('self', 'http://example.com/people/123'), Link.new('school', 'http://gatech.edu') ] # Build this information as JSON require 'jsonify' json = Jsonify::Builder.new(:format => :pretty) # Representation of the person json.alumnus do json.fname @person.first_name json.lname @person.last_name end # Relevant links json.links(@links) do |link| json.rel link.type json.href link.url end # Evaluate the result to a string json.compile! Results in ... { "alumnus": { "fname": "George", "lname": "Burdell" }, "links": [ { "rel": "self", "href": "http://example.com/people/123" }, { "rel": "school", "href": "http://gatech.edu" } ] } #### Convenience methods Jsonify provides class-level convenience methods that 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. - `compile` - Compiles the given block; any options are passed the instantiated `Builder` - `pretty` - Compiles the given block; results are output in `pretty` format. - `plain` - Compiles the given block; results are output in `plain` (default) format. For example ... Jsonify::Builder.plain do |j| j.song 'Fearless' j.album 'Meddle' end ### Rails View Templates 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. The Jsonify template handler exposes the `Jsonify::Builder` instance to your template with the `json` variable as in the following example: json.hello do json.world "Jsonify is Working!" end Just like with any other template, 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. #### Partials You can use partials from Jsonify views, and you can create Jsonify partials. How your Jsonify template uses a partial depends on how the information the partial returns is structured. Keep in mind that any paritial, be it a Jsonify template, erb, or anything else, always a returns its result as a string. ##### Jsonify partials Any Jsonify partial — that is, the file has a `.jsonify` extension — will return, by design, a string that is valid JSON. It will represent either a JSON object,wrapped in curly braces ( {} ), or a JSON array, wrapped in square brackets ( [] ). To incorporate such a value into a Jsonify template, use the `ingest!` method. `ingest!` assumes that the value it receives is valid JSON representation. It parses the JSON into a Jsonify object graph, and then adds it to the current Jsonify builder. Let's assume this this is your main template, `index.jsonify`: json << 1 json.ingest! (render :partial=>'my_partial') From the first line, you can tell that an array will be created as this line uses the append operator. On the second line, a partial is being added to the builder. Note that 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. Let's say that the partial file, `_my_partial.jsonify`, is as follows: json << 3 json << 4 This `json` variable in this partial is a separate distinct `Jsonify::Builder` instance from the `json` variable in the main template. > Note: Figure out if a the `json` instance can be passed to the Jsonify partial. > It would make things easier and we wouldn't have to ingest the result. This partial results in the following string: "[3,4]" The `ingest!` method will actually parse this string back into a Jsonify-based object, and adds it to the builder's current state. The resulting output will be: "[1,[3,4]]" ##### Other partials 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. For example, suppose you have the partial `_today.erb` with the following content: <%= Date.today %> You can then incorporate this partial into your Jsonify template just as you would any other string value: json << 1 json << {:date => (render :partial => 'today')} renders ... [1,{"date":"2011-07-30"}] ### Tilt Integration 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. require 'bundler/setup' require 'sinatra' require 'jsonify' require 'jsonify/tilt' helpers do def jsonify(*args) render(:jsonify, *args) end end get '/' do jsonify :index end And the corresponding template in `views\index.jsonify` json.hello :frank ### Usage Patterns Jsonify is designed to support construction of an valid JSON representation and is entirely based on the [JSON specification](http://json.org). JSON is built on two fundamental structures: * __object__: a collection of name-value pairs -- in Jsonify this is a `JsonObject` * __array__: an ordered list of values -- in Jsonify this is a `JsonArray` Jsonify adheres to the JSON specification and provides explicit support for working with these primary structures. At the top most level, a JSON string must be one of these structures and Jsonify ensures that this condition is met. #### JSON Objects 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 must have unique keys and the last one in, wins. json = Jsonify::Builder.new json.person do # start a new JsonObject where the key is 'foo' json.name 'George Burdell' # add a pair to this object json.skills ['engineering','bombing'] # adds a pair with an array value json.name 'George P. Burdell' end compiles to ... { "person": { "name": "George P. Burdell", "skills": [ "engineering", "bombing" ] } } It's perfectly legitimate for a JSON representation to simply be a collection of name-value pairs without a ___root___ element. Jsonify supports this by simply allowing you to specify the pairs that make up the object. json = Jsonify::Builder.new json.location 'Library Coffeehouse' json.neighborhood 'Brookhaven' compiles to ... { "location": "Library Coffeehouse", "neighborhood": "Brookhaven" } If the ___name___ you want contains whitespace or other characters not allowed in a Ruby method name, use `tag!`. json.tag!("my location", 'Library Coffeehouse') json.neighborhood 'Brookhaven' { "my location": "Library Coffeehouse", "neighborhood": "Brookhaven" } Jsonify also supports a hash-style interface for creating JSON objects. json = Jsonify::Builder.new json[:foo] = :bar json[:go] = :far compiles to ... { "foo": "bar", "go": "far" } You can these hash-style methods within a block as well ... json.homer do json[:beer] = "Duffs" json[:spouse] = "Marge" end compiles to ... { "homer": { "beer": "Duffs", "spouse": "Marge" } } If you prefer a more method-based approach, you can use the `store!` method passing it the key and value. json.store!(:foo, :bar) json.store!(:go, :far) #### JSON Arrays 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. Jsonify supports JSON array construction through two approaches: `method_missing` and `append!`. ##### method_missing Pass an array and a block to `method_missing` (or `tag!`), and Jsonify will create a JSON array. It will then iterate over your array and call the block for each item in the array. Within the block, you use the `json` object to add items to the JSON array. 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`) or symbol (for `tag!`). So this construct is really doing two things -- creating a JSON pair, and creating a JSON array as the value of the pair. Jsonify::Builder.pretty do |json| json.letters('a'..'c') do |letter| json << letter.upcase end end results in ... { "letters": [ "A", "B", "C" ] } Another way to handle this particular example is to get rid of the block entirely. Simply pass the array directly — the result will be the same. json.letters ('a'..'c').map(&:upcase) ##### append! But what if we don't want to start with an object? How do we tell Jsonify to start with an array instead? You can use `append!` (passing one or more values), or `<<` (which accepts only a single value) to the builder and it will assume you are adding values to a JSON array. json = Jsonify::Builder.new json.append! 'a'.upcase, 'b'.upcase, 'c'.upcase [ "A", "B", "C" ] or more idiomatically ... json.append! *('a'..'c').map(&:upcase) The append ___operator___, `<<`, can be used to push a single value into the array: json << 'a'.upcase json << 'b'.upcase json << 'c'.upcase Of course, standard iteration works here as well ... json = Jsonify::Builder.new ('a'..'c').each do |letter| json << letter.upcase end #### Mixing JSON Arrays and Objects You can readily mix JSON arrays and objects and the Jsonify builder will do its best to keep things straight. Here's an example where we start off with an array; but then decide to throw in an object. json = Jsonify::Builder.new json.append! 1,2,3 json.say "go, cat go" compiles to ... [1,2,3,{"say":"go, cat go"}] When Jsonify detected that you were trying to add a JSON name-value pair to a JSON array, it converted that pair to a JSON object. Let's take a look at the inverse approach ... say, we are creating a JSON object; and then decide to add an array item ... json.foo 'bar' json.go 'far' json << 'baz' In this case, Jsonify decides from the first line that you are creating a JSON object. When it gets to the third line, it simply turns the single item ('baz') into a name-value pair with a `null` value: {"foo":"bar","go":"far","baz":null} ## Documentation [Yard Docs](http://rubydoc.info/github/bsiggelkow/jsonify/master/frames)

Related Projects

- [Argonaut](https://github.com/jbr/argonaut) - [JSON Builder](https://github.com/dewski/json_builder) - [RABL](https://github.com/nesquena/rabl) - [Representative](https://github.com/mdub/representative) - [Tokamak](https://github.com/abril/tokamak) ## License This project is released under the MIT license. ## Authors * [Bill Siggelkow](https://github.com/bsiggelkow) jsonify-0.4.1/lib/0000755000000000000000000000000012406721251012441 5ustar rootrootjsonify-0.4.1/lib/jsonify.rb0000644000000000000000000000023612406721251014450 0ustar rootrootrequire 'multi_json' require 'jsonify/blank_slate' require 'jsonify/version' require 'jsonify/json_value' require 'jsonify/generate' require 'jsonify/builder'jsonify-0.4.1/lib/jsonify/0000755000000000000000000000000012406721251014122 5ustar rootrootjsonify-0.4.1/lib/jsonify/tilt.rb0000644000000000000000000000015512406721251015424 0ustar rootrootrequire 'tilt' require 'tilt/template' require 'jsonify/template' Tilt.register Jsonify::Template, 'jsonify'jsonify-0.4.1/lib/jsonify/version.rb0000644000000000000000000000004712406721251016135 0ustar rootrootmodule Jsonify VERSION = "0.4.1" end jsonify-0.4.1/lib/jsonify/generate.rb0000644000000000000000000000276512406721251016253 0ustar rootrootmodule Jsonify # Provides a set of functions for creating JsonValues from Ruby objects. module Generate class << self # Coerces the given value into a JsonValue (or subclass), String, or Number. # # The coercion rules are based on the type (class) of the value as follows: # - +JsonValue+ => no coercion # - +String+ => no coercion # - +Numeric+ => no coercion # - +TrueClass+ => JsonTrue ( true ) # - +FalseClass+=> JsonFalse ( false ) # - +NilClass+ => JsonNull ( null ) # - +Array+ => JsonArray ( [1,2,3] ) # - +Hash+ => JsonObject ( {"\a":1,\"b\":2} ) # - +else+ => #to_s # # @param val value to coerce into a JsonValue. def value(val) case val when JsonValue, String, Numeric; val when TrueClass; @json_true ||= JsonTrue.new when FalseClass; @json_false ||= JsonFalse.new when NilClass; @json_null ||= JsonNull.new when Array; array_value val when Hash; object_value val else val.to_s end end def pair_value(key,val=nil) JsonPair.new(key,value(val)) end def object_value(hash) json_object = JsonObject.new hash.each { |key,val| json_object.add( pair_value(key, val) ) } json_object end def array_value(vals) JsonArray.new(Array(vals).map{ |v| value v }) end end end endjsonify-0.4.1/lib/jsonify/blank_slate.rb0000644000000000000000000000306012406721251016725 0ustar rootroot###################################################################### # Jsonify::BlankSlate is based on Jim Weirich's BlankSlate. # # Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org). # All rights reserved. # # BlankSlate provides an abstract base class with no predefined # methods (except for \_\_send__ and \_\_id__). # BlankSlate is useful as a base class when writing classes that # depend upon method_missing (e.g. dynamic proxies). # # This Jsonify implementation of BlankSlate is identical; with the # exception that it does not include the Kernel, Module, and Object # patches. # module Jsonify class BlankSlate class << self # Hide the method named +name+ in the BlankSlate class. Don't # hide +instance_eval+ or any method beginning with "__". def hide(name) if instance_methods.include?(name.to_s) and name !~ /^(__|instance_eval)/ @hidden_methods ||= {} @hidden_methods[name.to_sym] = instance_method(name) undef_method name end end def find_hidden_method(name) @hidden_methods ||= {} @hidden_methods[name] || superclass.find_hidden_method(name) end # Redefine a previously hidden method so that it may be called on a blank # slate object. def reveal(name) hidden_method = find_hidden_method(name) fail "Don't know how to reveal method '#{name}'" unless hidden_method define_method(name, hidden_method) end end instance_methods.each { |m| hide(m) } end endjsonify-0.4.1/lib/jsonify/template.rb0000644000000000000000000000135012406721251016261 0ustar rootroot module Jsonify class Template < Tilt::Template self.default_mime_type = 'application/json' def self.engine_initialized? defined? ::Jsonify end def initialize_engine require_template_library 'jsonify' end def prepare; end def evaluate(scope, locals, &block) return super(scope, locals, &block) if data.respond_to?(:to_str) json = ::Jsonify::Builder.new data.call(json) json.compile! end def precompiled_preamble(locals) return super if locals.include? :json "json = ::Jsonify::Builder.new\n#{super}" end def precompiled_postamble(locals) "json.compile!" end def precompiled_template(locals) data.to_str end end end jsonify-0.4.1/lib/jsonify/builder.rb0000644000000000000000000002202112406721251016072 0ustar rootrootmodule Jsonify class Builder < BlankSlate class << self # Compiles the given block into a JSON string without having to instantiate a Builder. # # @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 # @option options [symbol] :format Format for the resultant JSON string; # `: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 # `:plain`, no formatting (compact one-line JSON -- best for production) # def compile( options={} ) builder = self.new options yield builder builder.compile! end # Compiles the given block into a pretty JSON string without having to instantiate a Builder. def pretty(&block) compile( :format => :pretty, &block ) end # Compiles the given block into a plain (e.g. no newlines and whitespace) JSON string without having to instantiate a Builder. def plain(&block) compile( :format => :plain, &block ) end end # Initializes a new builder. The Jsonify::Builder works by keeping a stack of +JsonValue+s. # # @param [Hash] options the options to create with # @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 # @option options [symbol] :format Format for the resultant JSON string; # `: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 # `:plain`, no formatting (compact one-line JSON -- best for production) def initialize(options={}) @verify = options[:verify].nil? ? false : options[:verify] @pretty = options[:format].to_s == 'pretty' ? true : false reset! end # Clears the builder data def reset! @level = 0 @stack = [] end # 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. # # @param sym [String] the key for the pair # @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` # @param &block a code block the result of which will be used to populate the value for the JSON pair def tag!(sym, args=nil, &block) method_missing(sym, *args, &block) end # Adds a new JsonPair for each attribute in attrs by taking attr as key and value of that attribute in object. # # @param object [Object] Object to take values from # @param *attrs [Array] Array of attributes for JsonPair keys def attributes!(object, *attrs) attrs.each do |attr| method_missing attr, object.send(attr) end end # Compiles the JSON objects into a string representation. # If initialized with +:verify => true+, the compiled result will be verified by attempting to re-parse it using +MultiJson.load+. # If initialized with +:format => :pretty+, the compiled result will be parsed and encoded via +MultiJson.dump(, :pretty => true)+ # This method can be called without any side effects. You can call +compile!+ at any time, and multiple times if desired. # # @raise [TypeError] only if +:verify+ is set to true # @raise [JSON::ParseError] only if +:verify+ is set to true def compile! result = (@stack[0] || {}).encode_as_json MultiJson.load(result) if @verify result = MultiJson.dump(MultiJson.load(result), :pretty => true) if @pretty result end # Stores the key and value into a JSON object # @param key the key for the pair # @param value the value for the pair # @return self to allow for chaining def store!(key, value=nil) (@stack[@level] ||= JsonObject.new).add(key,value) self end alias_method :[]=, :store! # Append -- pushes the given object on the end of a JsonArray. def <<(val) __array @stack[@level].add val self end # Append -- pushes the given variable list objects on to the end of the JsonArray def append!(*args) __array args.each do |arg| @stack[@level].add arg end self end # Creates array of json objects in current element from array passed to this method. # Accepts block which yields each array element. # # @example Create array in root JSON element # json.array!(@links) do |link| # json.rel link.first # json.href link.last # end # # @example compiles to something like ... # [ # { # "rel": "self", # "href": "http://example.com/people/123" # }, # { # "rel": "school", # "href": "http://gatech.edu" # } # ] # def array!(args) __array args.each do |arg| @level += 1 yield arg @level -= 1 value = @stack.pop # If the object created was an array with a single value # assume that just the value should be added if (JsonArray === value && value.values.length <= 1) value = value.values.first end @stack[@level].add value end end # Adds a new JsonPair to the builder where the key of the pair is set to the method name # (`sym`). # When passed a block, the value of the pair is set to the result of that # block; otherwise, the value is set to the argument(s) (`args`). # # @example Create an object literal # json.person do # json.first_name @person.given_name # json.last_name @person.surname # end # # @example compiles to something like ... # "person": { # "first_name": "George", # "last_name": "Burdell" # } # # If a block is given and an argument is passed, the argument it is assumed to be an # Array (more specifically, an object that responds to `each`). # The argument is iterated over and each item is yielded to the block. # The result of the block becomes an array item of the JsonArray. # # @example Map an of array of links to an array of JSON objects # json.links(@links) do |link| # json.rel link.first # json.href link.last # end # # @example compiles to something like ... # "links": [ # { # "rel": "self", # "href": "http://example.com/people/123" # }, # { # "rel": "school", # "href": "http://gatech.edu" # } # ] # # @param *args [Array] iterates over the given array yielding each array item to the block; the result of which is added to a JsonArray def method_missing(sym, args=nil, &block) # When no block given, simply add the symbol and arg as key - value for a JsonPair to current return __store( sym, args ) unless block # In a block; create a JSON pair (with no value) and add it to the current object pair = Generate.pair_value(sym) __store pair # Now process the block @level += 1 if args.nil? block.call else array!(args, &block) end # Set the value on the pair to the object at the top of the stack pair.value = @stack[@level] # Pop current off the top of the stack; we are done with it at this point @stack.pop @level -= 1 end # Ingest a full JSON representation (either an oject or array) # into the builder. The value is parsed, objectified, and added to the # current value at the top of the stack. # # @param [String] json_string a full JSON string (e.g. from a rendered partial) def ingest!(json_string) return if json_string.empty? res = Jsonify::Generate.value(MultiJson.load(json_string)) current = @stack[@level] if current.nil? @stack[@level] = res elsif JsonObject === current if JsonObject === res @stack[@level].merge res else raise ArgumentError.new("Cannot add JSON array to JSON Object") end else # current is JsonArray @stack[@level].add res end end private # BlankSlate requires the __ names def __store(key,value=nil) pair = (JsonPair === key ? key : JsonPair.new(key, value)) (@stack[@level] ||= JsonObject.new).add(pair) end def __array @stack[@level] ||= JsonArray.new end end end jsonify-0.4.1/lib/jsonify/json_value.rb0000644000000000000000000000330412406721251016614 0ustar rootrootclass Object def encode_as_json MultiJson.dump self end end module Jsonify class JsonValue attr_accessor :values def initialize(values=nil) @values = values || [] end def encode_as_json wrap values.map {|v| v.encode_as_json}.join(',') end def add(jsonValue) values << Generate.value(jsonValue) end end class JsonObject < JsonValue def initialize(values=nil) @values = values || {} end def wrap(joined_values) "{#{joined_values}}" end def values @values.values end def add(key, val=nil) pair = ( JsonPair === key ? key : JsonPair.new(key, val) ) @values.store(pair.key, pair) end def merge(json_object) json_object.values.each do |pair| @values.store(pair.key, pair) end end alias_method :<<, :add end class JsonArray < JsonValue def wrap(joined_values) "[#{joined_values}]" end def add(value) if JsonPair === value # wrap JsonPair in a JsonObject object = JsonObject.new object.add value value = object end super(value) end alias_method :<<, :add end class JsonPair < JsonValue attr_accessor :key, :value def initialize(key, value=nil) @key = key.to_s @value = Generate.value(value) end def encode_as_json %Q{#{key.encode_as_json}:#{value.encode_as_json}} end end class JsonTrue < JsonValue def encode_as_json 'true' end end class JsonFalse < JsonValue def encode_as_json 'false' end end class JsonNull < JsonValue def encode_as_json 'null' end end endjsonify-0.4.1/spec/0000755000000000000000000000000012406721251012625 5ustar rootrootjsonify-0.4.1/spec/generate_spec.rb0000644000000000000000000000177612406721251015771 0ustar rootrootrequire 'spec_helper' describe Jsonify::Generate do let(:links) do { :links => [ {:rel => 'foo', :href => 'goo'}, {:rel => 'bar', :href => 'baz'} ] } end it 'should build json' do json = Jsonify::Generate result = json.value links expected = '{"links":[{"rel":"foo","href":"goo"},{"rel":"bar","href":"baz"}]}' MultiJson.load(result.encode_as_json).should == MultiJson.load(expected) end describe 'complex example' do let(:jsonifier) { Jsonify::Generate } it 'should work' do json = jsonifier.object_value( {"links" => jsonifier.array_value([ jsonifier.object_value( {"rel" => "foo", "href" => "goo"} ), jsonifier.object_value( {"rel" => "bar", "href" => "baz"} ) ]) } ) expected = "{\"links\":[{\"rel\":\"foo\",\"href\":\"goo\"},{\"rel\":\"bar\",\"href\":\"baz\"}]}" MultiJson.load(json.encode_as_json).should == MultiJson.load(expected) end end endjsonify-0.4.1/spec/spec_helper.rb0000644000000000000000000000022612406721251015443 0ustar rootrootrequire 'json' require 'benchmark' require 'bundler' require 'bundler/setup' require 'jsonify' require 'jsonify/tilt' RSpec.configure do |config| endjsonify-0.4.1/spec/json_value_spec.rb0000644000000000000000000000203612406721251016332 0ustar rootrootrequire 'spec_helper' describe Jsonify::JsonValue do describe Jsonify::JsonPair do let(:pair) { Jsonify::JsonPair.new('key','value') } it 'should be constructed of a key and value' do pair.key.should == 'key' end it 'should evaluate to key:value' do pair.encode_as_json.should == "\"key\":\"value\"" end end describe Jsonify::JsonTrue do it 'should have a value of true' do Jsonify::JsonTrue.new.encode_as_json.should == 'true' end end describe Jsonify::JsonFalse do it 'should have a value of false' do Jsonify::JsonFalse.new.encode_as_json.should == 'false' end end describe Jsonify::JsonNull do it 'should have a value of true' do Jsonify::JsonNull.new.encode_as_json.should == 'null' end end describe 'strings' do it 'should quote the value' do 'foo'.encode_as_json.should == "\"foo\"" end it 'should encode unicode' do unicode = 'goober'.concat(16) unicode.encode_as_json.should == "\"goober\\u0010\"" end end endjsonify-0.4.1/spec/builder_spec.rb0000644000000000000000000002324712406721251015622 0ustar rootrootrequire 'spec_helper' describe Jsonify::Builder do let(:json) { Jsonify::Builder.new } describe 'class methods' do it '#compile should compile' do Jsonify::Builder.compile do |j| j.foo 'bar' end.should == '{"foo":"bar"}' end it '#pretty should be pretty' do pretty_results = < true) json.stack << FooBar.new lambda{ json.compile! }.should raise_error(MultiJson::DecodeError) end end describe 'unicode characters' do it 'should properly encode' do json = Jsonify::Builder.new(:verify => true) json.foo 'bar'.concat(16) lambda { json.compile! }.should_not raise_error end end describe "pretty printing" do it "should not be pretty by default" do json.foo do json.bar 'baz' end non_pretty_results = '{"foo":{"bar":"baz"}}' json.compile!.should == non_pretty_results end it "should be pretty when asked for" do json = Jsonify::Builder.new(:format => :pretty) json.foo do json.bar 'baz' end pretty_results = < :bar} json << {:go => :far} json.compile!.should == '[{"foo":"bar"},{"go":"far"}]' end end describe 'objects' do it 'simple object should work' do json.foo :bar json.go :far expected = '{"foo":"bar","go":"far"}' MultiJson.load(json.compile!).should == MultiJson.load(expected) end it 'should handle arrays' do json[1] = [2, 3] json[4] = 5 MultiJson.load(json.compile!).should == MultiJson.load('{"1":[2,3],"4":5}') end end describe "attributes!" do it "should allow create object with attributes of another object" do object = stub(:id => 1, :name => 'foo') json.attributes!(object, :id, :name) MultiJson.load(json.compile!).should == {'id' => 1, 'name' => 'foo'} end end describe 'using blocks' do it 'should allow names with spaces using tag!' do json.tag!("foo foo") do json.tag!("bar bar") do json.tag!('buzz buzz','goo goo') end end expected = '{"foo foo":{"bar bar":{"buzz buzz":"goo goo"}}}' MultiJson.load(json.compile!).should == MultiJson.load(expected) end it 'complex hash' do json.foo do json.bar do json.baz 'goo' end end json.compile!.should == '{"foo":{"bar":{"baz":"goo"}}}' end it 'simple hash' do json.foo do json.baz :goo end json.compile!.should == '{"foo":{"baz":"goo"}}' end it 'hash with array' do json.foo do json << 1 json << 2 end json.compile!.should == '{"foo":[1,2]}' end it 'hash with array by iteration' do ary = [1,2,3] json.foo do ary.each do |n| json << (n * 2) end end json.compile!.should == '{"foo":[2,4,6]}' end it 'simple array with object' do json << 1 json << {:foo => :bar} json.compile!.should == '[1,{"foo":"bar"}]' end it 'simple array with object via method_missing' do json << 1 json << 2 json.foo :bar json.compile!.should == "[1,2,{\"foo\":\"bar\"}]" end it 'complex hash with array' do json.foo do json.bar do json.baz 'goo' json.years do json << 2011 json << 2012 end end end expected = "{\"foo\":{\"bar\":{\"baz\":\"goo\",\"years\":[2011,2012]}}}" MultiJson.load(json.compile!).should == MultiJson.load(expected) end end describe 'without blocks' do describe 'complex array' do it 'should work' do json.bar [1,2,{:foo => 'goo'}] expected = "{\"bar\":[1,2,{\"foo\":\"goo\"}]}" MultiJson.load(json.compile!).should == MultiJson.load(expected) end end describe 'object with null' do it 'should handle missing argument' do json.foo json.compile!.should == '{"foo":null}' end end end describe 'super complex example' do let(:links) { link_class = Struct.new(:url,:type) [ link_class.new('example.com', 'self'), link_class.new('foo.com', 'parent') ] } it 'should work using arrays' do json.result do json.person do json.fname 'George' json.lname 'Burdell' end json.links(links) do |link| json.href link.url json.rel link.type end end expected = "{\"result\":{\"person\":{\"fname\":\"George\",\"lname\":\"Burdell\"},\"links\":[{\"href\":\"example.com\",\"rel\":\"self\"},{\"href\":\"foo.com\",\"rel\":\"parent\"}]}}" MultiJson.load(json.compile!).should == MultiJson.load(expected) end end describe 'ingest!' do context 'a json object' do let(:json_string) { '{"my girl":"Friday","my daughter":"Wednesday"}' } context 'into' do it 'nothing -- should replace it' do json.ingest! json_string MultiJson.load(json.compile!).should == MultiJson.load(json_string) end it 'json object -- should merge' do json["my boy"] = "Monday" json["my girl"] = "Sunday" json.ingest! json_string expected = '{"my boy":"Monday","my girl":"Friday","my daughter":"Wednesday"}' MultiJson.load(json.compile!).should == MultiJson.load(expected) end it 'json array -- should add' do json << 1 << 2 json.ingest! json_string expected = '[1,2,{"my girl":"Friday","my daughter":"Wednesday"}]' MultiJson.load(json.compile!).should == MultiJson.load(expected) end end end context 'a json array' do let(:json_string) { '[1,2,3]' } context 'into' do it 'nothing -- should replace it' do json.ingest! json_string MultiJson.load(json.compile!).should == MultiJson.load(json_string) end it 'json object -- should raise error' do json["my boy"] = "Monday" json["my girl"] = "Sunday" lambda{ json.ingest! json_string }.should raise_error( ArgumentError ) end it 'json array -- should add' do json << 1 << 2 json.ingest! json_string expected = '[1,2,[1,2,3]]' MultiJson.load(json.compile!).should == MultiJson.load(expected) end end end end describe 'with new array style' do it 'should work' do results =[ {:id => 1, :kids => [{:id => 'a'},{:id => 'b'}]}, {:id => 2, :kids => [{:id => 'c'},{:id => 'd'}]}, ] json.results(results) do |result| json.id result[:id] json.children(result[:kids]) do |kid| json.id kid[:id] end end expected = '{"results":[{"id":1,"children":[{"id":"a"},{"id":"b"}]},{"id":2,"children":[{"id":"c"},{"id":"d"}]}]}' MultiJson.load(json.compile!).should == MultiJson.load(expected) end it 'simple append' do json.letters('a'..'c') do |letter| json << letter.upcase end expected = '{"letters":["A","B","C"]}' MultiJson.load(json.compile!).should == MultiJson.load(expected) end end describe 'array!' do it 'allow creating array from root' do json.array!([1, 2, 3]) do |number| json.id number json.times2 number * 2 end MultiJson.load(json.compile!).should == [ {'id' => 1, 'times2' => 2}, {'id' => 2, 'times2' => 4}, {'id' => 3, 'times2' => 6}, ] end end end jsonify-0.4.1/spec/jsonify_spec.rb0000644000000000000000000000013212406721251015641 0ustar rootrootrequire 'spec_helper' describe Jsonify do it('should be true') {true.should be_true} endjsonify-0.4.1/spec/hello_world.jsonify0000644000000000000000000000002212406721251016534 0ustar rootrootjson.hello 'world'jsonify-0.4.1/spec/template_spec.rb0000644000000000000000000000055612406721251016005 0ustar rootrootrequire 'spec_helper' describe Jsonify::Template do it 'should be associated with .jsonify files' do template = Tilt.new('spec/hello_world.jsonify') template.should be_a_kind_of(Jsonify::Template) end it 'should render the template' do template = Tilt.new('spec/hello_world.jsonify') template.render.should == "{\"hello\":\"world\"}" end end