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 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