json-projection 0.1.0 → 0.1.1
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/lib/json-projection/projector.rb +211 -0
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 779c9afb4cf147d1b4b0d63c895a5f4b433475e9
|
|
4
|
+
data.tar.gz: c7d47cc81038d3b0da4c8524ee8a560a935b772a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 234351fc085f508c49bf192fbbdcd3f19d48961ef1c0d5470fb046cc8b7cec00a214b37933b45966f1fa768f477535e96b046f4d5640507c886893d3cbb148d5
|
|
7
|
+
data.tar.gz: 6e4cfedb4829f40e3c54ca69b67f06b6319dba2bce71f08f9250c84f354cb9884a1594a5665b7284997c79fb5c265a693ceab178eae5bae0942fbd5a3bd9dfcb
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
require_relative 'parser'
|
|
2
|
+
|
|
3
|
+
module JsonProjection
|
|
4
|
+
class Projector
|
|
5
|
+
|
|
6
|
+
# Initialize a new projector with a stream. The stream is consumed until the
|
|
7
|
+
# JSON structure it contains is finished.
|
|
8
|
+
#
|
|
9
|
+
# stream :: IO
|
|
10
|
+
# IO stream to read data from.
|
|
11
|
+
#
|
|
12
|
+
# Returns nothing.
|
|
13
|
+
def initialize(stream)
|
|
14
|
+
@parser = Parser.new(stream)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Given a JSON schema of properties we are interested in, filter the input
|
|
18
|
+
# stream to just these properties.
|
|
19
|
+
#
|
|
20
|
+
# Note this is not a schema validator, the schema is _navigated_ to
|
|
21
|
+
# determine interesting-ness but if you specify a schema for a key that
|
|
22
|
+
# turns out to be a number it _will be included_. The projection only cares
|
|
23
|
+
# about whether things are interesting while advancing through the stream.
|
|
24
|
+
# To validate the schema, use another class on the resulting projection.
|
|
25
|
+
#
|
|
26
|
+
# schema :: nil | Hash<String, schema>
|
|
27
|
+
# Map of the keys we are interested in, recurses.
|
|
28
|
+
#
|
|
29
|
+
# Returns a Hash<String, Any> instance or raises a parser error.
|
|
30
|
+
def project(schema)
|
|
31
|
+
event = @parser.next_event
|
|
32
|
+
unless event.is_a?(StartDocument)
|
|
33
|
+
raise StandardError, "expected document start"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
value = filter_subtree(schema, next_event)
|
|
37
|
+
|
|
38
|
+
event = @parser.next_event
|
|
39
|
+
unless event.is_a?(EndDocument)
|
|
40
|
+
raise StandardError, "expected document end"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
value
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def next_event
|
|
49
|
+
@parser.next_event
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def filter_subtree(schema, event)
|
|
53
|
+
if event.is_a?(StartArray)
|
|
54
|
+
return filter_array_subtree(schema, event)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if event.is_a?(StartObject)
|
|
58
|
+
return filter_object_subtree(schema, event)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
raise StandardError, "cannot filter #{event.class} subtree"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def filter_array_subtree(schema, event)
|
|
65
|
+
unless event.is_a?(StartArray)
|
|
66
|
+
raise StandardError, "expected start array"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
result = []
|
|
70
|
+
|
|
71
|
+
while (value_event = next_event) != EndArray.new
|
|
72
|
+
value = if value_event.is_a?(StartObject) || value_event.is_a?(StartArray)
|
|
73
|
+
filter_subtree(schema, value_event)
|
|
74
|
+
else
|
|
75
|
+
build_subtree(value_event)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
result << value
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
result
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def filter_object_subtree(schema, event)
|
|
85
|
+
unless event.is_a?(StartObject)
|
|
86
|
+
raise StandardError, "expected start object"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
result = {}
|
|
90
|
+
|
|
91
|
+
while (event = next_event) != EndObject.new
|
|
92
|
+
key = event
|
|
93
|
+
unless key.is_a?(Key)
|
|
94
|
+
raise StandardError, "expected a key event"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# nil schema means reify the subtree from here on
|
|
98
|
+
# otherwise if the schema has a key for this we want it
|
|
99
|
+
is_interesting = schema.nil? || schema.key?(key.key)
|
|
100
|
+
|
|
101
|
+
if !is_interesting
|
|
102
|
+
ignore_value
|
|
103
|
+
next
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
value_event = next_event
|
|
107
|
+
|
|
108
|
+
value = if value_event.is_a?(StartObject) || value_event.is_a?(StartArray)
|
|
109
|
+
# objects can have subschemas, look it up then build the value using
|
|
110
|
+
# filter
|
|
111
|
+
key_schema = if schema.nil?
|
|
112
|
+
nil
|
|
113
|
+
else
|
|
114
|
+
schema[key.key]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
filter_subtree(key_schema, value_event)
|
|
118
|
+
else
|
|
119
|
+
build_subtree(value_event)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
result[key.key] = value
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
result
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def build_subtree(event)
|
|
129
|
+
case event
|
|
130
|
+
|
|
131
|
+
when Null, Boolean, Number, String
|
|
132
|
+
event.value
|
|
133
|
+
|
|
134
|
+
when StartArray
|
|
135
|
+
result = []
|
|
136
|
+
|
|
137
|
+
while (event = next_event) != EndArray.new
|
|
138
|
+
result << build_subtree(event)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
result
|
|
142
|
+
|
|
143
|
+
when StartObject
|
|
144
|
+
result = {}
|
|
145
|
+
|
|
146
|
+
while (event = next_event) != EndObject.new
|
|
147
|
+
key = event
|
|
148
|
+
unless key.is_a?(Key)
|
|
149
|
+
raise StandardError, "expected a key event"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
result[key.key] = build_subtree(next_event)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
result
|
|
156
|
+
|
|
157
|
+
else
|
|
158
|
+
raise StandardError, "cannot build subtree for #{event.class}"
|
|
159
|
+
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# After reading a key if we know we are not interested in the next value,
|
|
164
|
+
# read and discard all its stream events.
|
|
165
|
+
#
|
|
166
|
+
# Values can be simple (string, numeric, boolean, null) or compound (object
|
|
167
|
+
# or array).
|
|
168
|
+
#
|
|
169
|
+
# Returns nothing.
|
|
170
|
+
def ignore_value
|
|
171
|
+
value_event = next_event
|
|
172
|
+
|
|
173
|
+
if value_event.is_a?(Value)
|
|
174
|
+
return
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
if value_event.is_a?(StartObject)
|
|
178
|
+
ignore_container
|
|
179
|
+
return
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
if value_event.is_a?(StartArray)
|
|
183
|
+
ignore_container
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
raise StandardError, "unknown value type to ignore #{value_event.class}"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Given the start of an array or object, read until the closing event.
|
|
190
|
+
# Object structures can nest and this is considered.
|
|
191
|
+
#
|
|
192
|
+
# Returns nothing.
|
|
193
|
+
def ignore_container
|
|
194
|
+
depth = 1
|
|
195
|
+
|
|
196
|
+
increase_depth_on = [ StartObject.empty, StartArray.empty ]
|
|
197
|
+
decrease_depth_on = [ EndObject.empty, EndArray.empty ]
|
|
198
|
+
|
|
199
|
+
while depth > 0
|
|
200
|
+
event = next_event
|
|
201
|
+
|
|
202
|
+
if increase_depth_on.include?(event)
|
|
203
|
+
depth += 1
|
|
204
|
+
elsif decrease_depth_on.include?(event)
|
|
205
|
+
depth -= 1
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
end
|
|
211
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: json-projection
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keith Duncan
|
|
@@ -73,12 +73,13 @@ executables: []
|
|
|
73
73
|
extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
|
75
75
|
files:
|
|
76
|
-
- lib/json-projection.rb
|
|
77
|
-
- lib/json-projection/parser.rb
|
|
78
76
|
- lib/json-projection/parser/buffer.rb
|
|
79
77
|
- lib/json-projection/parser/errors.rb
|
|
80
78
|
- lib/json-projection/parser/events.rb
|
|
81
79
|
- lib/json-projection/parser/fifo.rb
|
|
80
|
+
- lib/json-projection/parser.rb
|
|
81
|
+
- lib/json-projection/projector.rb
|
|
82
|
+
- lib/json-projection.rb
|
|
82
83
|
homepage: https://github.com/keithduncan/json-projection
|
|
83
84
|
licenses:
|
|
84
85
|
- MIT
|