json-emitter 1.0.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 +71 -34
- 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,53 +54,70 @@ 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
|
-
|
64
|
-
|
65
|
-
`JsonEmitter.array` takes an `Enumerable` and returns a stream that generates chunks of JSON.
|
66
|
-
|
67
|
-
```ruby
|
68
|
-
JsonEmitter.array(enumerator).each { |json_chunk|
|
69
|
-
# write json_chunk somewhere
|
70
|
-
}
|
71
|
-
```
|
67
|
+
## Sending objects
|
72
68
|
|
73
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
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
|
91
107
|
```
|
92
108
|
|
93
|
-
|
109
|
+
### JsonEmitter.error
|
94
110
|
|
95
111
|
```ruby
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
99
118
|
```
|
100
119
|
|
101
|
-
|
120
|
+
## Returning errors
|
102
121
|
|
103
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.
|
104
123
|
|
@@ -129,10 +148,28 @@ One way is to always steam an object that includes an `errors` field. Any errors
|
|
129
148
|
})
|
130
149
|
```
|
131
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|
|
157
|
+
# write json_chunk somewhere
|
158
|
+
}
|
159
|
+
```
|
160
|
+
|
161
|
+
Streams have a `#write` method for writing directly to a `File` or `IO` object.
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
File.open("~/out.json", "w+") { |f|
|
165
|
+
JsonEmitter.array(enumerator).write f
|
166
|
+
}
|
167
|
+
```
|
168
|
+
|
132
169
|
# License
|
133
170
|
|
134
171
|
MIT License. See LICENSE for details.
|
135
172
|
|
136
173
|
# Copyright
|
137
174
|
|
138
|
-
|
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: 1.
|
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: []
|