json-emitter 0.2.0 → 1.1.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 +4 -4
- data/README.md +95 -27
- data/lib/json-emitter/buffered_stream.rb +2 -2
- data/lib/json-emitter/context.rb +30 -3
- data/lib/json-emitter/version.rb +1 -1
- data/lib/json-emitter.rb +31 -4
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e68f558ac45759cbf0ddd691b8b20f499433dbf2d6321a360280a10b5fdb4840
|
4
|
+
data.tar.gz: 86e900c436e45efcf09fff15061a9e325781699bbb98c4fed8ca7accbeab3b5c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea19aa1f2ec09eb2b9634af8f2dfc9455957696bf28a26cf01bd70b49ff4b81d88e891f6564517cf56490e9482cc00a8a2dc94a0aaa76441d2587a4f0cbb9024
|
7
|
+
data.tar.gz: d246a354e506ccefbd5c8de2b2db1ff7faa2bad31b9425c18d2dc5eadf9fd9ad78e49fa2384aec0bb9f8b6881a3d3b9ac872e3661d40468046ee9e057912fbf6
|
data/README.md
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
# JsonEmitter
|
1
|
+
# JsonEmitter
|
2
|
+
|
3
|
+
*NOTE this project may appear inactive, but that's just because it's **done** (I think). Enjoy!*
|
2
4
|
|
3
5
|
JsonEmitter is a library for efficiently generating very large bits of JSON in Ruby. Need to generate a JSON array of 10,000 database records without eating up all your RAM? No problem! Objects? Nested structures? JsonEmitter has you covered.
|
4
6
|
|
@@ -21,7 +23,7 @@ class OrdersController < ApplicationController
|
|
21
23
|
def index
|
22
24
|
headers["Content-Type"] = "application/json"
|
23
25
|
headers["Last-Modified"] = Time.now.ctime.to_s
|
24
|
-
self.response_body = JsonEmitter.array(enumerator) { |order|
|
26
|
+
self.response_body = JsonEmitter.array(enumerator, rack: env) { |order|
|
25
27
|
order.to_h
|
26
28
|
}
|
27
29
|
end
|
@@ -33,7 +35,7 @@ end
|
|
33
35
|
```ruby
|
34
36
|
get "/orders" do
|
35
37
|
content_type :json
|
36
|
-
JsonEmitter.array(enumerator) { |order|
|
38
|
+
JsonEmitter.array(enumerator, rack: env) { |order|
|
37
39
|
order.to_h
|
38
40
|
}
|
39
41
|
end
|
@@ -43,7 +45,7 @@ end
|
|
43
45
|
|
44
46
|
```ruby
|
45
47
|
get :orders do
|
46
|
-
stream JsonEmitter.array(enumerator) { |order|
|
48
|
+
stream JsonEmitter.array(enumerator, rack: env) { |order|
|
47
49
|
ApiV1::Entities::Order.new(order)
|
48
50
|
}
|
49
51
|
end
|
@@ -52,40 +54,106 @@ end
|
|
52
54
|
**Rack**
|
53
55
|
|
54
56
|
```ruby
|
55
|
-
|
56
|
-
|
57
|
-
order
|
57
|
+
map "/orders" do
|
58
|
+
run ->(env) {
|
59
|
+
stream = JsonEmitter.array(enumerator, rack: env) { |order|
|
60
|
+
order.to_h
|
61
|
+
}
|
62
|
+
[200, {"Content-Type" => "application/json"}, stream]
|
58
63
|
}
|
59
|
-
|
60
|
-
}
|
64
|
+
end
|
61
65
|
```
|
62
66
|
|
63
|
-
|
67
|
+
## Sending objects
|
64
68
|
|
65
|
-
|
66
|
-
|
67
|
-
```ruby
|
68
|
-
JsonEmitter.array(enumerator).each { |json_chunk|
|
69
|
-
# write json_chunk somewhere
|
70
|
-
}
|
71
|
-
```
|
72
|
-
|
73
|
-
`JsonEmitter.object` takes a `Hash` and returns a stream that generates chunks of JSON. Hash values can be literals, Enumerators, Arrays, other Hashes, or Procs that return any of those. Hashes and arrays may be nested.
|
69
|
+
You may also stream Hashes as JSON objects. Keys must be Strings or Symbols, but values may be anything: literals, Enumerators, Arrays, other Hashes, or Procs that return any of those.
|
74
70
|
|
75
71
|
```ruby
|
76
72
|
JsonEmitter.object({
|
77
73
|
orders: Order.find_each.lazy.map { |order|
|
78
74
|
{id: order.id, desc: order.description}
|
79
75
|
},
|
76
|
+
big_text: ->() { load_tons_of_text },
|
77
|
+
}, rack: env)
|
78
|
+
```
|
80
79
|
|
81
|
-
|
82
|
-
load_tons_of_text
|
83
|
-
},
|
80
|
+
## Rack middleware won't work!
|
84
81
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
82
|
+
**IMPORTANT** Your Rack middleware will be *finished* by the time your JSON is built! So if you're depending on middleware to set `Time.zone`, report exceptions, etc. it won't work here. Fortunately, you can use `JsonEmitter.wrap` and `JsonEmitter.error` as replacements.
|
83
|
+
|
84
|
+
Put these somewhere like `config/initializers/json_emitter.rb`.
|
85
|
+
|
86
|
+
### JsonEmitter.wrap
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
# Ensure that ActiveRecord connections are returned to the connection pool
|
90
|
+
JsonEmitter.wrap do
|
91
|
+
->(app) { ActiveRecord::Base.with_connection(&app.call) }
|
92
|
+
end
|
93
|
+
|
94
|
+
JsonEmitter.wrap do
|
95
|
+
# Get TZ at the call site
|
96
|
+
current_tz = Time.zone
|
97
|
+
|
98
|
+
# Return a Proc that restores the call site's TZ before building the JSON
|
99
|
+
->(app) {
|
100
|
+
default_tz = Time.zone
|
101
|
+
Time.zone = current_tz
|
102
|
+
res = app.call
|
103
|
+
Time.zone = default_tz
|
104
|
+
res
|
105
|
+
}
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
### JsonEmitter.error
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
JsonEmitter.error do |ex, context|
|
113
|
+
Airbrake.notify(ex, {
|
114
|
+
request_path: context.request&.path,
|
115
|
+
query_string: context.request&.query_string,
|
116
|
+
})
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
## Returning errors
|
121
|
+
|
122
|
+
When streaming an HTTP response, you can't change the response code once you start sending data. So if you hit an error after you start, you need another way to communicate errors to the client.
|
123
|
+
|
124
|
+
One way is to always steam an object that includes an `errors` field. Any errors will be collected while the `Enumerator` is running. After it's finished, they'll be added to the JSON object.
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
def get_data
|
128
|
+
errors = []
|
129
|
+
enum = Enumerator.new { |y|
|
130
|
+
finished = false
|
131
|
+
until finished
|
132
|
+
data, errs, finished = get_data_chunk
|
133
|
+
if errs
|
134
|
+
errors += errs
|
135
|
+
finished = true
|
136
|
+
next
|
137
|
+
end
|
138
|
+
data.each { x| y << x }
|
139
|
+
end
|
140
|
+
}
|
141
|
+
return enum, -> { errors }
|
142
|
+
end
|
143
|
+
|
144
|
+
items_enum, errors_proc = get_data
|
145
|
+
JsonEmitter.object({
|
146
|
+
items: items_enum,
|
147
|
+
errors: errors_proc,
|
148
|
+
})
|
149
|
+
```
|
150
|
+
|
151
|
+
# Non-HTTP uses
|
152
|
+
|
153
|
+
`JsonEmitter.array` takes an `Enumerable` and returns a stream that generates chunks of JSON.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
JsonEmitter.array(enumerator).each { |json_chunk|
|
89
157
|
# write json_chunk somewhere
|
90
158
|
}
|
91
159
|
```
|
@@ -104,4 +172,4 @@ MIT License. See LICENSE for details.
|
|
104
172
|
|
105
173
|
# Copyright
|
106
174
|
|
107
|
-
|
175
|
+
Copyright (c) 2019 Jordan Hollinger.
|
@@ -62,12 +62,12 @@ module JsonEmitter
|
|
62
62
|
|
63
63
|
def buffer
|
64
64
|
Enumerator.new { |y|
|
65
|
-
buff = ""
|
65
|
+
buff = +""
|
66
66
|
@enum.each { |str|
|
67
67
|
buff << str
|
68
68
|
if buff.bytesize >= @buffer_size
|
69
69
|
y << buff
|
70
|
-
buff = ""
|
70
|
+
buff = +""
|
71
71
|
end
|
72
72
|
}
|
73
73
|
y << buff unless buff.empty?
|
data/lib/json-emitter/context.rb
CHANGED
@@ -19,16 +19,43 @@ module JsonEmitter
|
|
19
19
|
@pass_through_errors << Puma::ConnectionError if defined? Puma::ConnectionError
|
20
20
|
end
|
21
21
|
|
22
|
+
#
|
22
23
|
# Wrap the enumeration in a block. It will be passed a callback which it must call to continue.
|
23
|
-
#
|
24
|
+
# Define your wrappers somewhere like config/initializers/json_emitter.rb.
|
25
|
+
#
|
26
|
+
# JsonEmitter.wrap do
|
27
|
+
# # Get TZ at the call site
|
28
|
+
# current_tz = Time.zone
|
29
|
+
#
|
30
|
+
# # Return a Proc that restores the call site's TZ before building the JSON
|
31
|
+
# ->(app) {
|
32
|
+
# default_tz = Time.zone
|
33
|
+
# Time.zone = current_tz
|
34
|
+
# res = app.call
|
35
|
+
# Time.zone = default_tz
|
36
|
+
# res
|
37
|
+
# }
|
38
|
+
# end
|
39
|
+
#
|
24
40
|
def wrap(&block)
|
25
41
|
if (wrapper = block.call)
|
26
42
|
@wrappers.unshift wrapper
|
27
43
|
end
|
28
44
|
end
|
29
45
|
|
30
|
-
#
|
31
|
-
#
|
46
|
+
#
|
47
|
+
# Add an error handler. If the callsite is in a Rack app, the context will have a
|
48
|
+
# Rack::Request in context.request.
|
49
|
+
#
|
50
|
+
# Define your error handlers somewhere like config/initializers/json_emitter.rb.
|
51
|
+
#
|
52
|
+
# JsonEmitter.error do |ex, context|
|
53
|
+
# Airbrake.notify(ex, {
|
54
|
+
# request_path: context.request&.path,
|
55
|
+
# query_string: context.request&.query_string,
|
56
|
+
# })
|
57
|
+
# end
|
58
|
+
#
|
32
59
|
def error(&handler)
|
33
60
|
@error_handlers += [handler]
|
34
61
|
end
|
data/lib/json-emitter/version.rb
CHANGED
data/lib/json-emitter.rb
CHANGED
@@ -104,14 +104,41 @@ module JsonEmitter
|
|
104
104
|
BufferedStream.new(emitter, buffer_size, unit: buffer_unit)
|
105
105
|
end
|
106
106
|
|
107
|
-
#
|
108
|
-
#
|
107
|
+
#
|
108
|
+
# Wrap the enumeration in a block. It will be passed a callback which it must call to continue.
|
109
|
+
# Define your wrappers somewhere like config/initializers/json_emitter.rb.
|
110
|
+
#
|
111
|
+
# JsonEmitter.wrap do
|
112
|
+
# # Get TZ at the call site
|
113
|
+
# current_tz = Time.zone
|
114
|
+
#
|
115
|
+
# # Return a Proc that restores the call site's TZ before building the JSON
|
116
|
+
# ->(app) {
|
117
|
+
# default_tz = Time.zone
|
118
|
+
# Time.zone = current_tz
|
119
|
+
# res = app.call
|
120
|
+
# Time.zone = default_tz
|
121
|
+
# res
|
122
|
+
# }
|
123
|
+
# end
|
124
|
+
#
|
109
125
|
def self.wrap(&wrapper)
|
110
126
|
@wrappers.unshift wrapper
|
111
127
|
end
|
112
128
|
|
113
|
-
#
|
114
|
-
#
|
129
|
+
#
|
130
|
+
# Add an error handler. If the callsite is in a Rack app, the context will have a
|
131
|
+
# Rack::Request in context.request.
|
132
|
+
#
|
133
|
+
# Define your error handlers somewhere like config/initializers/json_emitter.rb.
|
134
|
+
#
|
135
|
+
# JsonEmitter.error do |ex, context|
|
136
|
+
# Airbrake.notify(ex, {
|
137
|
+
# request_path: context.request&.path,
|
138
|
+
# query_string: context.request&.query_string,
|
139
|
+
# })
|
140
|
+
# end
|
141
|
+
#
|
115
142
|
def self.error(&handler)
|
116
143
|
@error_handlers << handler
|
117
144
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json-emitter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Hollinger
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-07-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: multi_json
|
@@ -41,7 +41,7 @@ homepage: https://jhollinger.github.io/json-emitter/
|
|
41
41
|
licenses:
|
42
42
|
- MIT
|
43
43
|
metadata: {}
|
44
|
-
post_install_message:
|
44
|
+
post_install_message:
|
45
45
|
rdoc_options: []
|
46
46
|
require_paths:
|
47
47
|
- lib
|
@@ -49,15 +49,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
49
49
|
requirements:
|
50
50
|
- - ">="
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version:
|
52
|
+
version: 3.1.0
|
53
53
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
54
|
requirements:
|
55
55
|
- - ">="
|
56
56
|
- !ruby/object:Gem::Version
|
57
57
|
version: '0'
|
58
58
|
requirements: []
|
59
|
-
rubygems_version: 3.
|
60
|
-
signing_key:
|
59
|
+
rubygems_version: 3.4.19
|
60
|
+
signing_key:
|
61
61
|
specification_version: 4
|
62
62
|
summary: Efficiently generate tons of JSON
|
63
63
|
test_files: []
|