json-projection 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed7412ad6cf14e0b9a5d5c7ae2667289d7e407b3
4
- data.tar.gz: 0912f5a454752fe11c589a65d08ab1f0cb291668
3
+ metadata.gz: 779c9afb4cf147d1b4b0d63c895a5f4b433475e9
4
+ data.tar.gz: c7d47cc81038d3b0da4c8524ee8a560a935b772a
5
5
  SHA512:
6
- metadata.gz: 9d5517cd1c7e2f69a08d0cada96df5cbd20f3a20d4152bea642e3b7920a81454b7cd8889399b1d538ffd5b74209249278336d34cf8253be810a86c52ef5ff992
7
- data.tar.gz: 57e8cb50913047a8801a30a5fbf9259098a367e5ae38c500e1cc6be41096be6c4b730bab7c5330db81eebb220420db888be3ed33987b2643040eb61049bd3d50
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.0
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