json-write-stream 1.0.0
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.
- checksums.yaml +7 -0
- data/Gemfile +14 -0
- data/History.txt +3 -0
- data/README.md +113 -0
- data/Rakefile +18 -0
- data/json-write-stream.gemspec +20 -0
- data/lib/json-write-stream.rb +29 -0
- data/lib/json-write-stream/stateful.rb +203 -0
- data/lib/json-write-stream/version.rb +5 -0
- data/lib/json-write-stream/yielding.rb +109 -0
- data/spec/json-write-stream_spec.rb +44 -0
- data/spec/shared_examples.rb +30 -0
- data/spec/spec_helper.rb +159 -0
- data/spec/stateful_spec.rb +131 -0
- data/spec/yielding_spec.rb +40 -0
- metadata +71 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a397871c57d6b78327b37cac6e9848b212b4a2d6
|
4
|
+
data.tar.gz: 2a0437147cacd67a906a1c1d34b266001ae1f2fc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6af47c318296dd8bfc6bc981fcb420e7ba9b42905cd08ac459668dae13c588cc67bc1159e00f98c06d740304d6c1c6b3c27a36b50457ebef9951254430665e20
|
7
|
+
data.tar.gz: 619674607994da2fe54e97608869ae70ebf81a5f9e9100f0e2771c7dddae56af537f262f4a997221a31d28c7609f1523351b9d7a50d5e6a978954ed562b8c4b4
|
data/Gemfile
ADDED
data/History.txt
ADDED
data/README.md
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
json-write-stream
|
2
|
+
=================
|
3
|
+
|
4
|
+
An easy, streaming way to generate JSON.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
`gem install json-write-stream`
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
require 'json-write-stream'
|
14
|
+
```
|
15
|
+
|
16
|
+
### Examples for the Impatient
|
17
|
+
|
18
|
+
There are two types of JSON write stream: one that uses blocks and `yield` to delimit arrays and objects, and one that's purely stateful. Here are two examples that produce the same output:
|
19
|
+
|
20
|
+
Yielding:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
stream = StringIO.new
|
24
|
+
JsonWriteStream.from_stream(stream) do |writer|
|
25
|
+
writer.write_object do |obj_writer|
|
26
|
+
obj_writer.write_key_value('foo', 'bar')
|
27
|
+
obj_writer.write_array('baz') do |arr_writer|
|
28
|
+
arr_writer.write_element('goo')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
Stateful:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
stream = StringIO.new
|
38
|
+
writer = JsonWriteStream.from_stream(stream)
|
39
|
+
writer.write_object
|
40
|
+
writer.write_key_value('foo', 'bar')
|
41
|
+
writer.write_array('baz')
|
42
|
+
writer.write_element('goo')
|
43
|
+
writer.close # automatically adds closing punctuation for all nested types
|
44
|
+
```
|
45
|
+
|
46
|
+
Output:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
stream.string # => {"foo":"bar","baz":["goo"]}
|
50
|
+
```
|
51
|
+
|
52
|
+
### Yielding Writers
|
53
|
+
|
54
|
+
As far as yielding writers go, the example above contains everything you need. The stream will be automatically closed when the outermost block terminates.
|
55
|
+
|
56
|
+
### Stateful Writers
|
57
|
+
|
58
|
+
Stateful writers have a number of additional methods:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
stream = StringIO.new
|
62
|
+
writer = JsonWriteStream.from_stream(stream)
|
63
|
+
writer.write_object
|
64
|
+
|
65
|
+
writer.in_object? # => true, currently writing an object
|
66
|
+
writer.in_array? # => false, not currently writing an array
|
67
|
+
writer.eos? # => false, the stream is open and the outermost object hasn't been closed yet
|
68
|
+
|
69
|
+
writer.close_object # explicitly close the current object
|
70
|
+
writer.eos? # => true, the outermost object has been closed
|
71
|
+
|
72
|
+
writer.write_array # => raises JsonWriteStream::EndOfStreamError
|
73
|
+
writer.close_array # => raises JsonWriteStream::NotInArrayError
|
74
|
+
|
75
|
+
writer.closed? # => false, the stream is still open
|
76
|
+
writer.close # close the stream
|
77
|
+
writer.closed? # => true, the stream has been closed
|
78
|
+
```
|
79
|
+
|
80
|
+
### Writing to a File
|
81
|
+
|
82
|
+
JsonWriteStream also supports streaming to a file via the `open` method:
|
83
|
+
|
84
|
+
Yielding:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
JsonWriteStream.open('path/to/file.json') do |writer|
|
88
|
+
writer.write_object do |obj_writer|
|
89
|
+
...
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
Stateful:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
writer = JsonWriteStream.open('path/to/file.json')
|
98
|
+
writer.write_object
|
99
|
+
...
|
100
|
+
writer.close
|
101
|
+
```
|
102
|
+
|
103
|
+
## Requirements
|
104
|
+
|
105
|
+
No external requirements.
|
106
|
+
|
107
|
+
## Running Tests
|
108
|
+
|
109
|
+
`bundle exec rake` should do the trick. Alternatively you can run `bundle exec rspec`, which does the same thing.
|
110
|
+
|
111
|
+
## Authors
|
112
|
+
|
113
|
+
* Cameron C. Dutro: http://github.com/camertron
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'rubygems' unless ENV['NO_RUBYGEMS']
|
4
|
+
|
5
|
+
require 'bundler'
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
require 'rubygems/package_task'
|
8
|
+
|
9
|
+
require './lib/json-write-stream'
|
10
|
+
|
11
|
+
Bundler::GemHelper.install_tasks
|
12
|
+
|
13
|
+
task :default => :spec
|
14
|
+
|
15
|
+
desc 'Run specs'
|
16
|
+
RSpec::Core::RakeTask.new do |t|
|
17
|
+
t.pattern = './spec/**/*_spec.rb'
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), 'lib')
|
2
|
+
require 'json-write-stream/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "json-write-stream"
|
6
|
+
s.version = ::JsonWriteStream::VERSION
|
7
|
+
s.authors = ["Cameron Dutro"]
|
8
|
+
s.email = ["camertron@gmail.com"]
|
9
|
+
s.homepage = "http://github.com/camertron"
|
10
|
+
|
11
|
+
s.description = s.summary = "An easy, streaming way to generate JSON."
|
12
|
+
|
13
|
+
s.add_dependency 'json_pure', '~> 1.8.0'
|
14
|
+
|
15
|
+
s.platform = Gem::Platform::RUBY
|
16
|
+
s.has_rdoc = true
|
17
|
+
|
18
|
+
s.require_path = 'lib'
|
19
|
+
s.files = Dir["{lib,spec}/**/*", "Gemfile", "History.txt", "README.md", "Rakefile", "json-write-stream.gemspec"]
|
20
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'json-write-stream/yielding'
|
5
|
+
require 'json-write-stream/stateful'
|
6
|
+
|
7
|
+
class JsonWriteStream
|
8
|
+
class << self
|
9
|
+
def from_stream(stream)
|
10
|
+
if block_given?
|
11
|
+
yield writer = YieldingWriter.new(stream)
|
12
|
+
writer.close
|
13
|
+
else
|
14
|
+
StatefulWriter.new(stream)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def open(file)
|
19
|
+
handle = File.open(file, 'w')
|
20
|
+
|
21
|
+
if block_given?
|
22
|
+
yield writer = YieldingWriter.new(handle)
|
23
|
+
writer.close
|
24
|
+
else
|
25
|
+
StatefulWriter.new(handle)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
class JsonWriteStream
|
4
|
+
class NotInObjectError < StandardError; end
|
5
|
+
class NotInArrayError < StandardError; end
|
6
|
+
class EndOfStreamError < StandardError; end
|
7
|
+
|
8
|
+
class StatefulWriter
|
9
|
+
attr_reader :stream, :index, :stack, :closed
|
10
|
+
alias :closed? :closed
|
11
|
+
|
12
|
+
def initialize(stream)
|
13
|
+
@stream = stream
|
14
|
+
@index = 0
|
15
|
+
@stack = []
|
16
|
+
@closed = false
|
17
|
+
after_initialize
|
18
|
+
end
|
19
|
+
|
20
|
+
def after_initialize
|
21
|
+
end
|
22
|
+
|
23
|
+
def write_object(*args)
|
24
|
+
check_eos
|
25
|
+
current.write_object(*args) if current
|
26
|
+
stack.push(StatefulObjectWriter.new(stream))
|
27
|
+
end
|
28
|
+
|
29
|
+
def write_array(*args)
|
30
|
+
check_eos
|
31
|
+
current.write_array(*args) if current
|
32
|
+
stack.push(StatefulArrayWriter.new(stream))
|
33
|
+
end
|
34
|
+
|
35
|
+
def write_key_value(*args)
|
36
|
+
check_eos
|
37
|
+
current.write_key_value(*args)
|
38
|
+
end
|
39
|
+
|
40
|
+
def write_element(*args)
|
41
|
+
check_eos
|
42
|
+
current.write_element(*args)
|
43
|
+
end
|
44
|
+
|
45
|
+
def close_object
|
46
|
+
if in_object?
|
47
|
+
stack.pop.close
|
48
|
+
current.increment if current
|
49
|
+
increment
|
50
|
+
else
|
51
|
+
raise NotInObjectError, 'not currently writing an object.'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def close_array
|
56
|
+
if in_array?
|
57
|
+
stack.pop.close
|
58
|
+
current.increment if current
|
59
|
+
increment
|
60
|
+
else
|
61
|
+
raise NotInArrayError, 'not currently writing an array.'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def close
|
66
|
+
until stack.empty?
|
67
|
+
if in_object?
|
68
|
+
close_object
|
69
|
+
else
|
70
|
+
close_array
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
stream.close
|
75
|
+
@closed = true
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def in_object?
|
80
|
+
current ? current.is_object? : false
|
81
|
+
end
|
82
|
+
|
83
|
+
def in_array?
|
84
|
+
current ? current.is_array? : false
|
85
|
+
end
|
86
|
+
|
87
|
+
def eos?
|
88
|
+
(stack.size == 0 && index > 0) || closed?
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def check_eos
|
94
|
+
if eos?
|
95
|
+
raise EndOfStreamError, 'end of stream.'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def current
|
100
|
+
stack.last
|
101
|
+
end
|
102
|
+
|
103
|
+
def escape(str)
|
104
|
+
JSON.generate([str])[1..-2]
|
105
|
+
end
|
106
|
+
|
107
|
+
def write_comma
|
108
|
+
stream.write(',') if index > 0
|
109
|
+
end
|
110
|
+
|
111
|
+
def increment
|
112
|
+
@index += 1
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class StatefulObjectWriter < StatefulWriter
|
117
|
+
def after_initialize
|
118
|
+
stream.write('{')
|
119
|
+
end
|
120
|
+
|
121
|
+
# prep work (array is written afterwards)
|
122
|
+
def write_array(key)
|
123
|
+
write_comma
|
124
|
+
increment
|
125
|
+
write_key(key)
|
126
|
+
stream.write(':')
|
127
|
+
end
|
128
|
+
|
129
|
+
# prep work (object is written afterwards)
|
130
|
+
def write_object(key)
|
131
|
+
write_comma
|
132
|
+
increment
|
133
|
+
write_key(key)
|
134
|
+
stream.write(':')
|
135
|
+
end
|
136
|
+
|
137
|
+
def write_key_value(key, value)
|
138
|
+
write_comma
|
139
|
+
increment
|
140
|
+
write_key(key)
|
141
|
+
stream.write(":#{escape(value)}")
|
142
|
+
end
|
143
|
+
|
144
|
+
def close
|
145
|
+
stream.write('}')
|
146
|
+
end
|
147
|
+
|
148
|
+
def is_object?
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
def is_array?
|
153
|
+
false
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def write_key(key)
|
159
|
+
case key
|
160
|
+
when String
|
161
|
+
stream.write(escape(key))
|
162
|
+
else
|
163
|
+
raise ArgumentError, "'#{key}' must be a string"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class StatefulArrayWriter < StatefulWriter
|
169
|
+
def after_initialize
|
170
|
+
stream.write('[')
|
171
|
+
end
|
172
|
+
|
173
|
+
def write_element(element)
|
174
|
+
write_comma
|
175
|
+
increment
|
176
|
+
stream.write(escape(element))
|
177
|
+
end
|
178
|
+
|
179
|
+
# prep work
|
180
|
+
def write_array
|
181
|
+
write_comma
|
182
|
+
increment
|
183
|
+
end
|
184
|
+
|
185
|
+
# prep work
|
186
|
+
def write_object
|
187
|
+
write_comma
|
188
|
+
increment
|
189
|
+
end
|
190
|
+
|
191
|
+
def close
|
192
|
+
stream.write(']')
|
193
|
+
end
|
194
|
+
|
195
|
+
def is_object?
|
196
|
+
false
|
197
|
+
end
|
198
|
+
|
199
|
+
def is_array?
|
200
|
+
true
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
class JsonWriteStream
|
4
|
+
class YieldingWriter
|
5
|
+
attr_reader :stream, :index
|
6
|
+
|
7
|
+
def initialize(stream)
|
8
|
+
@stream = stream
|
9
|
+
@index = 0
|
10
|
+
after_initialize
|
11
|
+
end
|
12
|
+
|
13
|
+
def after_initialize
|
14
|
+
end
|
15
|
+
|
16
|
+
def write_object(comma_written = false)
|
17
|
+
unless comma_written
|
18
|
+
write_comma
|
19
|
+
increment
|
20
|
+
end
|
21
|
+
|
22
|
+
yield writer = YieldingObjectWriter.new(stream)
|
23
|
+
writer.close
|
24
|
+
end
|
25
|
+
|
26
|
+
def write_array(comma_written = false)
|
27
|
+
unless comma_written
|
28
|
+
write_comma
|
29
|
+
increment
|
30
|
+
end
|
31
|
+
|
32
|
+
yield writer = YieldingArrayWriter.new(stream)
|
33
|
+
writer.close
|
34
|
+
end
|
35
|
+
|
36
|
+
def close
|
37
|
+
stream.close
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def escape(str)
|
43
|
+
JSON.generate([str])[1..-2]
|
44
|
+
end
|
45
|
+
|
46
|
+
def write_comma
|
47
|
+
stream.write(',') if index > 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def increment
|
51
|
+
@index += 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class YieldingObjectWriter < YieldingWriter
|
56
|
+
def after_initialize
|
57
|
+
stream.write('{')
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_array(key)
|
61
|
+
write_comma
|
62
|
+
increment
|
63
|
+
write_key(key)
|
64
|
+
stream.write(':')
|
65
|
+
super(true)
|
66
|
+
end
|
67
|
+
|
68
|
+
def write_object(key)
|
69
|
+
write_comma
|
70
|
+
increment
|
71
|
+
write_key(key)
|
72
|
+
stream.write(':')
|
73
|
+
super(true)
|
74
|
+
end
|
75
|
+
|
76
|
+
def write_key_value(key, value)
|
77
|
+
write_comma
|
78
|
+
increment
|
79
|
+
write_key(key)
|
80
|
+
stream.write(":#{escape(value)}")
|
81
|
+
end
|
82
|
+
|
83
|
+
def close
|
84
|
+
stream.write('}')
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def write_key(key)
|
90
|
+
stream.write(escape(key.to_s))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class YieldingArrayWriter < YieldingWriter
|
95
|
+
def after_initialize
|
96
|
+
stream.write('[')
|
97
|
+
end
|
98
|
+
|
99
|
+
def write_element(element)
|
100
|
+
write_comma
|
101
|
+
increment
|
102
|
+
stream.write(escape(element))
|
103
|
+
end
|
104
|
+
|
105
|
+
def close
|
106
|
+
stream.write(']')
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
describe JsonWriteStream do
|
7
|
+
let(:yielding_writer) { JsonWriteStream::YieldingWriter }
|
8
|
+
let(:stateful_writer) { JsonWriteStream::StatefulWriter }
|
9
|
+
let(:stream_writer) { JsonWriteStream }
|
10
|
+
let(:tempfile) { Tempfile.new('temp') }
|
11
|
+
let(:stream) { StringIO.new }
|
12
|
+
|
13
|
+
describe '#from_stream' do
|
14
|
+
it 'yields a yielding stream if given a block' do
|
15
|
+
stream_writer.from_stream(stream) do |writer|
|
16
|
+
expect(writer).to be_a(yielding_writer)
|
17
|
+
expect(writer.stream).to equal(stream)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns a stateful writer if not given a block' do
|
22
|
+
writer = stream_writer.from_stream(stream)
|
23
|
+
expect(writer).to be_a(stateful_writer)
|
24
|
+
expect(writer.stream).to equal(stream)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#open' do
|
29
|
+
it 'opens a file and yields a yielding stream if given a block' do
|
30
|
+
mock.proxy(File).open(tempfile, 'w')
|
31
|
+
stream_writer.open(tempfile) do |writer|
|
32
|
+
expect(writer).to be_a(yielding_writer)
|
33
|
+
expect(writer.stream.path).to eq(tempfile.path)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'opens a file and returns a stateful writer if not given a block' do
|
38
|
+
mock.proxy(File).open(tempfile, 'w')
|
39
|
+
writer = stream_writer.open(tempfile)
|
40
|
+
expect(writer).to be_a(stateful_writer)
|
41
|
+
expect(writer.stream.path).to eq(tempfile.path)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
shared_examples 'a json stream' do
|
4
|
+
it 'handles a simple array' do
|
5
|
+
check_roundtrip(['abc'])
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'handles a simple object' do
|
9
|
+
check_roundtrip({ 'foo' => 'bar' })
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'handles one level of array nesting' do
|
13
|
+
check_roundtrip([['def'],'abc'])
|
14
|
+
check_roundtrip(['abc',['def']])
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'handles one level of object nesting' do
|
18
|
+
check_roundtrip({ 'foo' => { 'bar' => 'baz' } })
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'handles one level of mixed nesting' do
|
22
|
+
check_roundtrip({ 'foo' => ['bar', 'baz'] })
|
23
|
+
check_roundtrip([{ 'foo' => 'bar' }])
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'handles multiple levels of mixed nesting' do
|
27
|
+
check_roundtrip({'foo' => ['bar', { 'baz' => 'moo', 'gaz' => ['doo'] }, 'kal'], 'jim' => ['jill', ['john']] })
|
28
|
+
check_roundtrip(['foo', { 'bar' => 'baz', 'moo' => ['gaz', ['jim', ['jill']], 'jam'] }])
|
29
|
+
end
|
30
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'json-write-stream'
|
5
|
+
require 'shared_examples'
|
6
|
+
require 'pry-nav'
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.mock_with :rr
|
10
|
+
end
|
11
|
+
|
12
|
+
class RoundtripChecker
|
13
|
+
class << self
|
14
|
+
include RSpec::Matchers
|
15
|
+
|
16
|
+
def check_roundtrip(obj)
|
17
|
+
stream = StringIO.new
|
18
|
+
writer = create_writer(stream)
|
19
|
+
serialize(obj, writer)
|
20
|
+
new_obj = JSON.parse(stream.string)
|
21
|
+
compare(obj, new_obj)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def compare(old_obj, new_obj)
|
27
|
+
expect(old_obj.class).to equal(new_obj.class)
|
28
|
+
|
29
|
+
case old_obj
|
30
|
+
when Hash
|
31
|
+
expect(old_obj.keys).to eq(new_obj.keys)
|
32
|
+
|
33
|
+
old_obj.each_pair do |key, old_val|
|
34
|
+
compare(old_val, new_obj[key])
|
35
|
+
end
|
36
|
+
when Array
|
37
|
+
old_obj.each_with_index do |old_element, idx|
|
38
|
+
compare(old_element, new_obj[idx])
|
39
|
+
end
|
40
|
+
else
|
41
|
+
expect(old_obj).to eq(new_obj)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class YieldingRoundtripChecker < RoundtripChecker
|
48
|
+
class << self
|
49
|
+
protected
|
50
|
+
|
51
|
+
def create_writer(stream)
|
52
|
+
JsonWriteStream::YieldingWriter.new(stream)
|
53
|
+
end
|
54
|
+
|
55
|
+
def serialize(obj, writer)
|
56
|
+
case obj
|
57
|
+
when Hash
|
58
|
+
writer.write_object do |object_writer|
|
59
|
+
serialize_object(obj, object_writer)
|
60
|
+
end
|
61
|
+
when Array
|
62
|
+
writer.write_array do |array_writer|
|
63
|
+
serialize_array(obj, array_writer)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def serialize_object(obj, writer)
|
69
|
+
obj.each_pair do |key, val|
|
70
|
+
case val
|
71
|
+
when Hash
|
72
|
+
writer.write_object(key) do |object_writer|
|
73
|
+
serialize_object(val, object_writer)
|
74
|
+
end
|
75
|
+
when Array
|
76
|
+
writer.write_array(key) do |array_writer|
|
77
|
+
serialize_array(val, array_writer)
|
78
|
+
end
|
79
|
+
else
|
80
|
+
writer.write_key_value(key, val)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def serialize_array(obj, writer)
|
86
|
+
obj.each do |element|
|
87
|
+
case element
|
88
|
+
when Hash
|
89
|
+
writer.write_object do |object_writer|
|
90
|
+
serialize_object(element, object_writer)
|
91
|
+
end
|
92
|
+
when Array
|
93
|
+
writer.write_array do |array_writer|
|
94
|
+
serialize_array(element, array_writer)
|
95
|
+
end
|
96
|
+
else
|
97
|
+
writer.write_element(element)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class StatefulRoundtripChecker < RoundtripChecker
|
105
|
+
class << self
|
106
|
+
protected
|
107
|
+
|
108
|
+
def create_writer(stream)
|
109
|
+
JsonWriteStream::StatefulWriter.new(stream)
|
110
|
+
end
|
111
|
+
|
112
|
+
def serialize(obj, writer)
|
113
|
+
case obj
|
114
|
+
when Hash
|
115
|
+
writer.write_object
|
116
|
+
serialize_object(obj, writer)
|
117
|
+
writer.close_object
|
118
|
+
when Array
|
119
|
+
writer.write_array
|
120
|
+
serialize_array(obj, writer)
|
121
|
+
writer.close_array
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def serialize_object(obj, writer)
|
126
|
+
obj.each_pair do |key, val|
|
127
|
+
case val
|
128
|
+
when Hash
|
129
|
+
writer.write_object(key)
|
130
|
+
serialize_object(val, writer)
|
131
|
+
writer.close_object
|
132
|
+
when Array
|
133
|
+
writer.write_array(key)
|
134
|
+
serialize_array(val, writer)
|
135
|
+
writer.close_array
|
136
|
+
else
|
137
|
+
writer.write_key_value(key, val)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def serialize_array(obj, writer)
|
143
|
+
obj.each do |element|
|
144
|
+
case element
|
145
|
+
when Hash
|
146
|
+
writer.write_object
|
147
|
+
serialize_object(element, writer)
|
148
|
+
writer.close_object
|
149
|
+
when Array
|
150
|
+
writer.write_array
|
151
|
+
serialize_array(element, writer)
|
152
|
+
writer.close_array
|
153
|
+
else
|
154
|
+
writer.write_element(element)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe JsonWriteStream::YieldingWriter do
|
6
|
+
let(:stream) { StringIO.new }
|
7
|
+
let(:stream_writer) { JsonWriteStream::StatefulWriter.new(stream) }
|
8
|
+
|
9
|
+
def check_roundtrip(obj)
|
10
|
+
StatefulRoundtripChecker.check_roundtrip(obj)
|
11
|
+
end
|
12
|
+
|
13
|
+
it_behaves_like 'a json stream'
|
14
|
+
|
15
|
+
describe '#close' do
|
16
|
+
it 'unwinds the stack, adds appropriate closing punctuation for each unclosed item, and closes the stream' do
|
17
|
+
stream_writer.write_array
|
18
|
+
stream_writer.write_element('abc')
|
19
|
+
stream_writer.write_object
|
20
|
+
stream_writer.write_key_value('def', 'ghi')
|
21
|
+
stream_writer.close
|
22
|
+
|
23
|
+
expect(stream.string).to eq('["abc",{"def":"ghi"}]')
|
24
|
+
expect(stream_writer).to be_closed
|
25
|
+
expect(stream).to be_closed
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#closed?' do
|
30
|
+
it 'returns false if the stream is still open' do
|
31
|
+
expect(stream_writer).to_not be_closed
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns true if the stream is closed' do
|
35
|
+
stream_writer.close
|
36
|
+
expect(stream_writer).to be_closed
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#in_object?' do
|
41
|
+
it 'returns true if the writer is currently writing an object' do
|
42
|
+
stream_writer.write_object
|
43
|
+
expect(stream_writer).to be_in_object
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'returns false if the writer is not currently writing an object' do
|
47
|
+
expect(stream_writer).to_not be_in_object
|
48
|
+
stream_writer.write_array
|
49
|
+
expect(stream_writer).to_not be_in_object
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#in_array?' do
|
54
|
+
it 'returns true if the writer is currently writing an array' do
|
55
|
+
stream_writer.write_array
|
56
|
+
expect(stream_writer).to be_in_array
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'returns false if the writer is not currently writing an array' do
|
60
|
+
expect(stream_writer).to_not be_in_array
|
61
|
+
stream_writer.write_object
|
62
|
+
expect(stream_writer).to_not be_in_array
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#eos?' do
|
67
|
+
it 'returns false if nothing has been written yet' do
|
68
|
+
expect(stream_writer).to_not be_eos
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns false if the writer is in the middle of writing' do
|
72
|
+
stream_writer.write_object
|
73
|
+
expect(stream_writer).to_not be_eos
|
74
|
+
end
|
75
|
+
|
76
|
+
it "returns true if the writer has finished it's top-level" do
|
77
|
+
stream_writer.write_object
|
78
|
+
stream_writer.close_object
|
79
|
+
expect(stream_writer).to be_eos
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'returns true if the writer is closed' do
|
83
|
+
stream_writer.close
|
84
|
+
expect(stream_writer).to be_eos
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#close_object' do
|
89
|
+
it 'raises an error if an object is not currently being written' do
|
90
|
+
stream_writer.write_array
|
91
|
+
expect(-> { stream_writer.close_object }).to raise_error(JsonWriteStream::NotInObjectError)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#close_array' do
|
96
|
+
it 'raises an error if an array is not currently being written' do
|
97
|
+
stream_writer.write_object
|
98
|
+
expect(-> { stream_writer.close_array }).to raise_error(JsonWriteStream::NotInArrayError)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'with a closed stream writer' do
|
103
|
+
before(:each) do
|
104
|
+
stream_writer.close
|
105
|
+
end
|
106
|
+
|
107
|
+
describe '#write_object' do
|
108
|
+
it 'raises an error if eos' do
|
109
|
+
expect(-> { stream_writer.write_object }).to raise_error(JsonWriteStream::EndOfStreamError)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '#write_array' do
|
114
|
+
it 'raises an error if eos' do
|
115
|
+
expect(-> { stream_writer.write_object }).to raise_error(JsonWriteStream::EndOfStreamError)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe '#write_key_value' do
|
120
|
+
it 'raises an error if eos' do
|
121
|
+
expect(-> { stream_writer.write_key_value('abc', 'def') }).to raise_error(JsonWriteStream::EndOfStreamError)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe '#write_element' do
|
126
|
+
it 'raises an error if eos' do
|
127
|
+
expect(-> { stream_writer.write_element('foo') }).to raise_error(JsonWriteStream::EndOfStreamError)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe JsonWriteStream::YieldingWriter do
|
6
|
+
let(:stream) { StringIO.new }
|
7
|
+
let(:stream_writer) { JsonWriteStream::YieldingWriter.new(stream) }
|
8
|
+
|
9
|
+
def check_roundtrip(obj)
|
10
|
+
YieldingRoundtripChecker.check_roundtrip(obj)
|
11
|
+
end
|
12
|
+
|
13
|
+
it_behaves_like 'a json stream'
|
14
|
+
|
15
|
+
describe '#write_key_value' do
|
16
|
+
it 'converts all keys to strings' do
|
17
|
+
stream_writer.write_object do |object_writer|
|
18
|
+
object_writer.write_key_value(123, 'abc')
|
19
|
+
end
|
20
|
+
|
21
|
+
expect(stream.string).to eq('{"123":"abc"}')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'supports non-string values' do
|
25
|
+
stream_writer.write_object do |object_writer|
|
26
|
+
object_writer.write_key_value('abc', 123)
|
27
|
+
object_writer.write_key_value('def', true)
|
28
|
+
end
|
29
|
+
|
30
|
+
expect(stream.string).to eq('{"abc":123,"def":true}')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#close' do
|
35
|
+
it 'closes the underlying stream' do
|
36
|
+
stream_writer.close
|
37
|
+
expect(stream).to be_closed
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: json-write-stream
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cameron Dutro
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json_pure
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.8.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.8.0
|
27
|
+
description: An easy, streaming way to generate JSON.
|
28
|
+
email:
|
29
|
+
- camertron@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- Gemfile
|
35
|
+
- History.txt
|
36
|
+
- README.md
|
37
|
+
- Rakefile
|
38
|
+
- json-write-stream.gemspec
|
39
|
+
- lib/json-write-stream.rb
|
40
|
+
- lib/json-write-stream/stateful.rb
|
41
|
+
- lib/json-write-stream/version.rb
|
42
|
+
- lib/json-write-stream/yielding.rb
|
43
|
+
- spec/json-write-stream_spec.rb
|
44
|
+
- spec/shared_examples.rb
|
45
|
+
- spec/spec_helper.rb
|
46
|
+
- spec/stateful_spec.rb
|
47
|
+
- spec/yielding_spec.rb
|
48
|
+
homepage: http://github.com/camertron
|
49
|
+
licenses: []
|
50
|
+
metadata: {}
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 2.2.2
|
68
|
+
signing_key:
|
69
|
+
specification_version: 4
|
70
|
+
summary: An easy, streaming way to generate JSON.
|
71
|
+
test_files: []
|