jsonify 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in jsonify.gemspec
3
+ # Gem dependencies are in jsonify.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Jsonify -- a builder for JSON <a href="http://travis-ci.org/bsiggelkow/jsonify"><img src="https://secure.travis-ci.org/bsiggelkow/jsonify.png" alt=""></a>
1
+ # Jsonify &mdash; a builder for JSON [![Build Status](http://travis-ci.org/bsiggelkow/jsonify.png)](http://travis-ci.org/bsiggelkow/jsonify)
2
2
 
3
3
  [Jsonify](https://github.com/bsiggelkow/jsonify) is to JSON as [Builder](https://github.com/jimweirich/builder) is to XML.
4
4
 
@@ -31,6 +31,12 @@ But an even greater motivation for me was emulating the simplicity of [Builder](
31
31
 
32
32
  ## Usage
33
33
 
34
+ In the examples that follow, the JSON output is usually shown "prettified". Is this only
35
+ for illustration purposes, as the default behavior for Jsonify is not to prettify the output.
36
+ You can enable prettification by passing `:pretty => true` to the Jsonify::Builder constructor; however,
37
+ pretty printing is a relatively costly operation and should not be used in production (unless, of course, you explicitly
38
+ want to show this format).
39
+
34
40
  ### Standalone
35
41
  # Create some objects that represent a person and associated hyperlinks
36
42
  @person = Struct.new(:first_name,:last_name).new('George','Burdell')
@@ -86,6 +92,242 @@ The Jsonify template handler exposes the `Jsonify::Builder` instance to your tem
86
92
  json.world "Jsonify is Working!"
87
93
  end
88
94
 
95
+ #### Partials
96
+
97
+ You can use partials with Jsonify view templates, but how you use them will
98
+ depend on how the information they return is structured. An important to keep in
99
+ mind is that a partial, no matter what kind it is, always a returns a string.
100
+
101
+ ##### Jsonify partials
102
+
103
+ Any Jsonify partial &mdash; that is, the file has a `.jsonify` extension &mdash;
104
+ will return, by design, a string that is valid JSON. It will represent either a JSON object,
105
+ and be wrapped in curly braces ({}), or a JSON array, and be wrapped in square brackets ([]).
106
+
107
+ To incorporate such a value into a Jsonify template, Jsonify provides the `ingest!` method.
108
+
109
+ You can use this method to add JSON, rendered by a partial, to the builder.
110
+ Let's assume this our main template, `index.jsonify`:
111
+
112
+ json << 1
113
+ json.ingest! (render :partial=>'my_partial')
114
+
115
+ From the first line, you can tell that an array will be created so this line uses the append operator.
116
+ On the second line, a partial is being added to the builder. Note that you cannot simply place `render :parial ...`
117
+ on a line by itself as you can do with other templates like `erb` and `haml`; you have to explicitly tell Jsonify
118
+ to add it.
119
+
120
+ Let's say that the partial file, `_my_partial.jsonify`, is as follows:
121
+
122
+ json << 3
123
+ json << 4
124
+
125
+ This `json` variable is a separate instance of the Jsonify::Builder, distinct from the builder instance,
126
+ in the main template. This partial will end up generating the following string:
127
+
128
+ "[3,4]"
129
+
130
+ The `ingest!` method will actually parse this string back into a Jsonify-based object, and add it
131
+ to the builder's current state. The resulting output will be:
132
+
133
+ "[1,[3,4]]"
134
+
135
+ ##### Other partials
136
+
137
+ You can also use output from non-Jsonify templates (e.g. erb); just remember that the output from a template
138
+ is always a string and that you have to tell the builder how to include the result of the partial.
139
+ For example, suppose I have the partial `_today.erb` with the following content:
140
+
141
+ <%= Date.today %>
142
+
143
+ You can then incorporate this partial into your Jsonify template just as you would any other string value:
144
+
145
+ json << 1
146
+ json.ingest! (render :partial=>'my_partial')
147
+ json << {:date => (render :partial => 'today')}
148
+
149
+ renders ...
150
+
151
+ [1,[3,4],{"date":"2011-07-30"}]
152
+
153
+ ### Usage Patterns
154
+
155
+ Jsonify is designed to support construction of an valid JSON representation and
156
+ is entirely based on the [JSON specification](http://json.org).
157
+
158
+ JSON is built on two fundamental structures:
159
+
160
+ * __object__: a collection of name-value pairs -- in Jsonify this is a `JsonObject`
161
+ * __array__: an ordered list of values -- in Jsonify this is a `JsonArray`
162
+
163
+ Jsonify adheres to the JSON specification and provides explicit support
164
+ for working with these primary structures. At the top most level, a JSON string
165
+ must be one of these structures and Jsonify ensures that this condition is met.
166
+
167
+ #### JSON Objects
168
+
169
+ A JSON object, sometimes
170
+ referred to as an ___object literal___, is a common structure familiar
171
+ to most developers. Its analogous to the nested element structured common
172
+ in XML. The [JSON RFC](http://www.ietf.org/rfc/rfc4627.txt) states that
173
+ "the names within an object SHOULD be unique". Jsonify elevates this recommendation
174
+ by backing the JsonObject with a `Hash`; an object must have unique keys and the last one in, wins.
175
+
176
+ json = Jsonify::Builder.new
177
+ json.person do # start a new JsonObject where the key is 'foo'
178
+ json.name 'George Burdell' # add a pair to this object
179
+ json.skills ['engineering','bombing'] # adds a pair with an array value
180
+ json.name 'George P. Burdell'
181
+ end
182
+
183
+ compiles to ...
184
+
185
+ {
186
+ "person": {
187
+ "name": "George P. Burdell",
188
+ "skills": [
189
+ "engineering",
190
+ "bombing"
191
+ ]
192
+ }
193
+ }
194
+
195
+ It's perfectly legitimate for a JSON representation to simply be a collection
196
+ of name-value pairs without a ___root___ element. Jsonify supports this by
197
+ simply allowing you to specify the pairs that make up the object.
198
+
199
+ json = Jsonify::Builder.new
200
+ json.location 'Library Coffeehouse'
201
+ json.neighborhood 'Brookhaven'
202
+
203
+ compiles to ...
204
+
205
+ {
206
+ "location": "Library Coffeehouse",
207
+ "neighborhood": "Brookhaven"
208
+ }
209
+
210
+ If the ___name___ you want contains whitespace or other characters not allowed in a Ruby method name, use `tag!`.
211
+
212
+ json.tag!("my location", 'Library Coffeehouse')
213
+ json.neighborhood 'Brookhaven'
214
+
215
+ {
216
+ "my location": "Library Coffeehouse",
217
+ "neighborhood": "Brookhaven"
218
+ }
219
+
220
+ Jsonify also supports a hash-style interface for creating JSON objects.
221
+
222
+ json = Jsonify::Builder.new
223
+
224
+ json[:foo] = :bar
225
+ json[:go] = :far
226
+
227
+ compiles to ...
228
+
229
+ {
230
+ "foo": "bar",
231
+ "go": "far"
232
+ }
233
+
234
+ You can these hash-style methods within a block as well ...
235
+
236
+ json.homer do
237
+ json[:beer] = "Duffs"
238
+ json[:spouse] = "Marge"
239
+ end
240
+
241
+ compiles to ...
242
+
243
+ {
244
+ "homer": {
245
+ "beer": "Duffs",
246
+ "spouse": "Marge"
247
+ }
248
+ }
249
+
250
+ If you prefer a more method-based approach, you can use the `store!` method passing it the key and value.
251
+
252
+ json.store!(:foo, :bar)
253
+ json.store!(:go, :far)
254
+
255
+ #### JSON Arrays
256
+
257
+ A JSON array is an ordered list of JSON values. A JSON value can be a simple value,
258
+ like a string or a number, or a supported JavaScript primitive like true, false, or null.
259
+ A JSON value can also be a JSON object or another JSON array. Jsonify strives to make
260
+ this kind of construction possible in a buider-style.
261
+
262
+ Jsonify supports JSON array construction through two approaches: `method_missing` and `append!`.
263
+
264
+ ##### method_missing
265
+
266
+ Pass an array and a block to `method_missing` (or `tag!`), and Jsonify will iterate
267
+ over that array, and create a JSON array where each array item is the result of the block.
268
+ If you pass an array that has a length of 5, you will end up with a JSON array that has 5 items.
269
+ 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`)
270
+ or symbol (for `tag!`).
271
+
272
+ So this construct is really doing two things -- creating a JSON pair, and creating a JSON array as the value of the pair.
273
+
274
+ json = Jsonify::Builder.new(:pretty => true)
275
+ json.letters('a'..'c') do |letter|
276
+ letter.upcase
277
+ end
278
+
279
+ compiles to ...
280
+
281
+ {
282
+ "letters": [
283
+ "A",
284
+ "B",
285
+ "C"
286
+ ]
287
+ }
288
+
289
+ Another way to handle this particular example is to get rid of the block entirely.
290
+ Simply pass the array directly &mdash; the result will be the same.
291
+
292
+ json.letters ('a'..'c').map(&:upcase)
293
+
294
+ ##### append!
295
+
296
+ But what if we don't want to start with an object? How do we tell Jsonify to start with an array instead?
297
+
298
+ You can use `append!` (passing one or more values), or `<<` (which accepts only a single value) to
299
+ the builder and it will assume you are adding values to a JSON array.
300
+
301
+ json = Jsonify::Builder.new
302
+ json.append! 'a'.upcase, 'b'.upcase, 'c'.upcase
303
+
304
+ [
305
+ "A",
306
+ "B",
307
+ "C"
308
+ ]
309
+
310
+ or more idiomatically ...
311
+
312
+ json.append! *('a'..'c').map(&:upcase)
313
+
314
+ The append ___operator___, `<<`, can be used to push a single value into the array:
315
+
316
+ json << 'a'.upcase
317
+ json << 'b'.upcase
318
+ json << 'c'.upcase
319
+
320
+ Of course, standard iteration works here as well ...
321
+
322
+ json = Jsonify::Builder.new
323
+ ('a'..'c').each do |letter|
324
+ json << letter.upcase
325
+ end
326
+
327
+ #### Mixing JSON Arrays and Objects
328
+
329
+ ___coming soon___
330
+
89
331
  ## Documentation
90
332
 
91
333
  [Yard Docs](http://rubydoc.info/github/bsiggelkow/jsonify/master/frames)
@@ -95,16 +337,18 @@ The Jsonify template handler exposes the `Jsonify::Builder` instance to your tem
95
337
  - [Argonaut](https://github.com/jbr/argonaut)
96
338
  - [JSON Builder](https://github.com/dewski/json_builder)
97
339
  - [RABL](https://github.com/nesquena/rabl)
340
+ - [Representative](https://github.com/mdub/representative)
98
341
  - [Tokamak](https://github.com/abril/tokamak)
99
342
 
100
343
  ## TODOs
101
344
  1. Benchmark performance
102
345
  1. Document how partials can be used
346
+ 1. Clean up specs
103
347
 
104
348
  ## Roadmap
105
349
 
106
350
  1. Split Rails template handling into separate gem
107
- 1. Add support for Sinatra and Padrino (maybe separate gems)
351
+ 1. Add support for Sinatra and Padrino (Tilt integration?)
108
352
 
109
353
  ## License
110
354
 
@@ -134,12 +134,35 @@ module Jsonify
134
134
  @level -= 1
135
135
  end
136
136
 
137
+ # Ingest a full JSON representation (either an oject or array)
138
+ # into the builder. The value is parsed, objectified, and added to the
139
+ # current value at the top of the stack.
140
+ #
141
+ # @param [String] json_string a full JSON string (e.g. from a rendered partial)
142
+ def ingest!(json_string)
143
+ return if json_string.empty?
144
+ res = Jsonify::Generate.value(JSON.parse(json_string))
145
+ current = @stack[@level]
146
+ if current.nil?
147
+ @stack[@level] = res
148
+ elsif JsonObject === current
149
+ if JsonObject === res
150
+ @stack[@level].merge res
151
+ else
152
+ raise ArgumentError.new("Cannot add JSON array to JSON Object")
153
+ end
154
+ else # current is JsonArray
155
+ @stack[@level].add res
156
+ end
157
+ end
158
+
137
159
  private
138
160
 
139
161
  # BlankSlate requires the __<method> names
140
162
 
141
163
  def __store(key,value=nil)
142
- (@stack[@level] ||= JsonObject.new).add(key,value)
164
+ pair = (JsonPair === key ? key : JsonPair.new(key, value))
165
+ (@stack[@level] ||= JsonObject.new).add(pair)
143
166
  end
144
167
 
145
168
  def __append(value)
@@ -30,9 +30,15 @@ module Jsonify
30
30
  end
31
31
 
32
32
  def add(key, val=nil)
33
- pair = (JsonPair === key) ? key : JsonPair.new(key, val)
33
+ pair = ( JsonPair === key ? key : JsonPair.new(key, val) )
34
34
  @values.store(pair.key, pair)
35
35
  end
36
+
37
+ def merge(json_object)
38
+ json_object.values.each do |pair|
39
+ @values.store(pair.key, pair)
40
+ end
41
+ end
36
42
 
37
43
  alias_method :<<, :add
38
44
  alias_method :add!, :add # for consistency with the Builder api
@@ -1,3 +1,3 @@
1
1
  module Jsonify
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -70,8 +70,7 @@ PRETTY_JSON
70
70
  json.compile!.should == "[1,2]"
71
71
  end
72
72
  it 'with the append! method' do
73
- json.append!( 1,2 )
74
- .append! 3
73
+ json.append!( 1,2 ).append! 3
75
74
  json.compile!.should == "[1,2,3]"
76
75
  end
77
76
  end
@@ -81,9 +80,8 @@ PRETTY_JSON
81
80
  json.compile!.should == '{"foo":"bar"}'
82
81
  end
83
82
  it 'should support the store! message' do
84
- json.store!( "foo", "bar" )
85
- .store!( 'no', "whar" )
86
- json.compile!.should == '{"foo":"bar","no":"whar"}'
83
+ json.store!( "foo", "bar" ).store!( 'no', "whar" )
84
+ JSON.parse(json.compile!).should == JSON.parse('{"foo":"bar","no":"whar"}')
87
85
  end
88
86
  end
89
87
  end
@@ -103,7 +101,7 @@ PRETTY_JSON
103
101
  it 'array of hashes should work' do
104
102
  json << {:foo => :bar}
105
103
  json << {:go => :far}
106
- json.compile!.should == "[{\"foo\":\"bar\"},{\"go\":\"far\"}]"
104
+ json.compile!.should == '[{"foo":"bar"},{"go":"far"}]'
107
105
  end
108
106
  end
109
107
 
@@ -111,7 +109,8 @@ PRETTY_JSON
111
109
  it 'simple object should work' do
112
110
  json.foo :bar
113
111
  json.go :far
114
- json.compile!.should == "{\"foo\":\"bar\",\"go\":\"far\"}"
112
+ expected = '{"foo":"bar","go":"far"}'
113
+ JSON.parse(json.compile!).should == JSON.parse(expected)
115
114
  end
116
115
  it 'should handle arrays' do
117
116
  json[1] = [2, 3]
@@ -128,7 +127,8 @@ PRETTY_JSON
128
127
  json.tag!('buzz buzz','goo goo')
129
128
  end
130
129
  end
131
- json.compile!.should == "{\"foo foo\":{\"bar bar\":{\"buzz buzz\":\"goo goo\"}}}"
130
+ expected = '{"foo foo":{"bar bar":{"buzz buzz":"goo goo"}}}'
131
+ JSON.parse(json.compile!).should == JSON.parse(expected)
132
132
  end
133
133
 
134
134
  it 'complex hash' do
@@ -137,14 +137,14 @@ PRETTY_JSON
137
137
  json.baz 'goo'
138
138
  end
139
139
  end
140
- json.compile!.should == "{\"foo\":{\"bar\":{\"baz\":\"goo\"}}}"
140
+ json.compile!.should == '{"foo":{"bar":{"baz":"goo"}}}'
141
141
  end
142
142
 
143
143
  it 'simple hash' do
144
144
  json.foo do
145
145
  json.baz :goo
146
146
  end
147
- json.compile!.should == "{\"foo\":{\"baz\":\"goo\"}}"
147
+ json.compile!.should == '{"foo":{"baz":"goo"}}'
148
148
  end
149
149
 
150
150
  it 'hash with array' do
@@ -152,7 +152,7 @@ PRETTY_JSON
152
152
  json << 1
153
153
  json << 2
154
154
  end
155
- json.compile!.should == "{\"foo\":[1,2]}"
155
+ json.compile!.should == '{"foo":[1,2]}'
156
156
  end
157
157
 
158
158
  it 'hash with array by iteration' do
@@ -160,13 +160,20 @@ PRETTY_JSON
160
160
  json.foo(ary) do |n|
161
161
  n * 2
162
162
  end
163
- json.compile!.should == "{\"foo\":[2,4,6]}"
163
+ json.compile!.should == '{"foo":[2,4,6]}'
164
164
  end
165
165
 
166
166
  it 'simple array with object' do
167
167
  json << 1
168
168
  json << {:foo => :bar}
169
- json.compile!.should == "[1,{\"foo\":\"bar\"}]"
169
+ json.compile!.should == '[1,{"foo":"bar"}]'
170
+ end
171
+
172
+ it 'simple array with object via method_missing' do
173
+ json << 1
174
+ json << 2
175
+ json.foo :bar
176
+ json.compile!.should == "[1,2,{\"foo\":\"bar\"}]"
170
177
  end
171
178
 
172
179
  it 'complex hash with array' do
@@ -224,4 +231,49 @@ PRETTY_JSON
224
231
  JSON.parse(json.compile!).should == JSON.parse(expected)
225
232
  end
226
233
  end
234
+
235
+ describe 'ingest!' do
236
+ context 'a json object' do
237
+ let(:json_string) { '{"my girl":"Friday","my daughter":"Wednesday"}' }
238
+ context 'into' do
239
+ it 'nothing -- should replace it' do
240
+ json.ingest! json_string
241
+ JSON.parse(json.compile!).should == JSON.parse(json_string)
242
+ end
243
+ it 'json object -- should merge' do
244
+ json["my boy"] = "Monday"
245
+ json["my girl"] = "Sunday"
246
+ json.ingest! json_string
247
+ expected = '{"my boy":"Monday","my girl":"Friday","my daughter":"Wednesday"}'
248
+ JSON.parse(json.compile!).should == JSON.parse(expected)
249
+ end
250
+ it 'json array -- should add' do
251
+ json << 1 << 2
252
+ json.ingest! json_string
253
+ expected = '[1,2,{"my girl":"Friday","my daughter":"Wednesday"}]'
254
+ JSON.parse(json.compile!).should == JSON.parse(expected)
255
+ end
256
+ end
257
+ end
258
+ context 'a json array' do
259
+ let(:json_string) { '[1,2,3]' }
260
+ context 'into' do
261
+ it 'nothing -- should replace it' do
262
+ json.ingest! json_string
263
+ JSON.parse(json.compile!).should == JSON.parse(json_string)
264
+ end
265
+ it 'json object -- should raise error' do
266
+ json["my boy"] = "Monday"
267
+ json["my girl"] = "Sunday"
268
+ lambda{ json.ingest! json_string }.should raise_error( ArgumentError )
269
+ end
270
+ it 'json array -- should add' do
271
+ json << 1 << 2
272
+ json.ingest! json_string
273
+ expected = '[1,2,[1,2,3]]'
274
+ JSON.parse(json.compile!).should == JSON.parse(expected)
275
+ end
276
+ end
277
+ end
278
+ end
227
279
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: jsonify
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.3
5
+ version: 0.0.4
6
6
  platform: ruby
7
7
  authors:
8
8
  - Bill Siggelkow
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-07-27 00:00:00 -04:00
13
+ date: 2011-07-30 00:00:00 -04:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -144,7 +144,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
144
144
  requirements:
145
145
  - - ">="
146
146
  - !ruby/object:Gem::Version
147
- hash: 2505547202778738501
147
+ hash: -1768495453963674133
148
148
  segments:
149
149
  - 0
150
150
  version: "0"
@@ -153,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
153
  requirements:
154
154
  - - ">="
155
155
  - !ruby/object:Gem::Version
156
- hash: 2505547202778738501
156
+ hash: -1768495453963674133
157
157
  segments:
158
158
  - 0
159
159
  version: "0"