eco-helpers 3.0.18 → 3.0.19

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
  SHA256:
3
- metadata.gz: 47c28bbf13f50c0f3f895c40c0ad8532d13233e0e2b81faea474bbeb0a1c2308
4
- data.tar.gz: e13091192acd044b667cd20ebe7bcaf5c17da30c1ef83266fefa9ffa90fb8348
3
+ metadata.gz: 0f6637f51c2d892eb93c881cca7613fba034cbb00ff2676966172c2277f583ce
4
+ data.tar.gz: 68703625905aa5ed2c00223a628de16612714a2c2cdd69a1c0a284c8c6096332
5
5
  SHA512:
6
- metadata.gz: 02fe482f0c75e0926fe147e1ab3f4b0a0d1b826990bae3ad8ea97fb0aa53a800e1d208fb62e209877c0594b4f32792096967ec1f0528be8c81c3cd8c404c5131
7
- data.tar.gz: 4f9c12554b25c81243eac348f106392a092853c87204922d25cd1f25d287abcc23461df2b0a6a19b3dd3e4624f625e49ce5920ed04000ab6641f26312a38cb0f
6
+ metadata.gz: 89bbd57468fd7de3412240e3e44f61122aee9982896401494c4ee88c5e2dec97563e421cac2da0794c4f68ff90206422bc52b2868ee1e7ef3422fce50f3ec73d
7
+ data.tar.gz: 82703dafe2ccecf25994567926c80e7bd40fe0af70853cce664dbcbfc12b56b0b9ad72308ccf2789a386e053baadce2a8c29095130aa57dc560228d99c27267e
data/CHANGELOG.md CHANGED
@@ -2,10 +2,29 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [3.0.18] - 2024-10-xx
5
+ ## [3.0.19] - 2024-10-xx
6
6
 
7
7
  ### Added
8
8
 
9
+ - Input entries `:json` parser
10
+ - **Cases**:
11
+ - `json-to-csv`
12
+ - `split-json`
13
+ - `sort-csv`
14
+ - `group-csv`
15
+
16
+ ### Changed
17
+
18
+ - Refactored input file parsers (`csv`, `xls`)
19
+ - Upgraded gems:
20
+ - `ecoportal-api`
21
+ - `ecoportal-api-v2`
22
+ - `ecoportal-api-graphql`
23
+
24
+ ### Fixed
25
+
26
+ ## [3.0.18] - 2024-10-28
27
+
9
28
  ### Changed
10
29
 
11
30
  - upgrade `ecoportal-api` gem
@@ -15,8 +34,6 @@ All notable changes to this project will be documented in this file.
15
34
  - **added** auto-swap endpoint from `job` to `batch` on TimeOut errors.
16
35
  - **retries** also on `Ecoportal::API::Errors::StartTimeOut`
17
36
 
18
- ### Fixed
19
-
20
37
  ## [3.0.17] - 2024-10-18
21
38
 
22
39
  ### Added
data/eco-helpers.gemspec CHANGED
@@ -40,9 +40,9 @@ Gem::Specification.new do |spec|
40
40
  spec.add_dependency 'bcrypt_pbkdf', '~> 1.0'
41
41
  spec.add_dependency 'docx', '>= 0.8.0', '< 0.9'
42
42
  spec.add_dependency 'dotenv', '~> 3'
43
- spec.add_dependency 'ecoportal-api', '~> 0.10', '>= 0.10.6'
44
- spec.add_dependency 'ecoportal-api-graphql', '~> 0.4', '>= 0.4.2'
45
- spec.add_dependency 'ecoportal-api-v2', '~> 2.0', '>= 2.0.11'
43
+ spec.add_dependency 'ecoportal-api', '~> 0.10', '>= 0.10.7'
44
+ spec.add_dependency 'ecoportal-api-graphql', '~> 0.4', '>= 0.4.3'
45
+ spec.add_dependency 'ecoportal-api-v2', '~> 2.0', '>= 2.0.12'
46
46
  spec.add_dependency 'ed25519', '~> 1.2'
47
47
  spec.add_dependency 'fast_excel', '>= 0.5.0', '< 0.6'
48
48
  spec.add_dependency 'fuzzy_match', '>= 2.1.0', '< 2.2'
@@ -21,6 +21,7 @@ module Eco
21
21
  return [] if miss.empty?
22
22
  return attrs if match.empty?
23
23
  return miss if type == :all
24
+
24
25
  []
25
26
  end
26
27
  end
@@ -38,6 +39,7 @@ module Eco
38
39
  msg << "#{self.class}, is linked to"
39
40
  return @attribute || (raise msg)
40
41
  end
42
+
41
43
  name value
42
44
  @attribute = value
43
45
  end
@@ -62,6 +64,7 @@ module Eco
62
64
  def parsing_phase(phase = nil)
63
65
  @parsing_phase ||= :internal
64
66
  return @parsing_phase unless phase
67
+
65
68
  @parsing_phase = phase
66
69
  end
67
70
 
@@ -71,6 +74,7 @@ module Eco
71
74
  def serializing_phase(phase = nil)
72
75
  @serializing_phase ||= :person
73
76
  return @serializing_phase unless phase
77
+
74
78
  @serializing_phase = phase
75
79
  end
76
80
 
@@ -86,6 +90,7 @@ module Eco
86
90
  # Helper to build the `active_when` condition.
87
91
  def active_when_all(*attrs)
88
92
  @active_when_attrs = RequiredAttrs.new(attribute, :all, attrs)
93
+
89
94
  @active_when = proc do |source_data|
90
95
  keys = data_keys(source_data)
91
96
  attrs.all? {|key| keys.include?(key)}
@@ -149,6 +154,10 @@ module Eco
149
154
 
150
155
  private
151
156
 
157
+ def options
158
+ ASSETS.cli.options
159
+ end
160
+
152
161
  def _define_parser(attr_parser)
153
162
  if (active_when = self.class.active_when)
154
163
  attr_parser.def_parser(
@@ -166,6 +175,7 @@ module Eco
166
175
 
167
176
  def _define_serializer(attr_parser)
168
177
  return unless respond_to?(:serializer, true)
178
+
169
179
  attr_parser.def_serializer(
170
180
  self.class.serializing_phase,
171
181
  &method(:serializer)
@@ -1,235 +1,48 @@
1
1
  class Eco::API::Common::People::DefaultParsers::CSVParser < Eco::API::Common::Loaders::Parser
2
2
  attribute :csv
3
3
 
4
+ include Eco::API::Common::People::DefaultParsers::Helpers::ExpectedHeaders
5
+ include Eco::API::Common::People::DefaultParsers::Helpers::NullParsing
6
+
4
7
  def parser(data, deps)
5
8
  Eco::CSV.parse(data, headers: true, skip_blanks: true).tap do |table|
6
- require_headers!(table)
7
- check_headers(table) if deps[:check_headers] && check_headers?
8
- end.each_with_object([]) do |row, arr_hash|
9
- row_hash = row.headers.uniq.each_with_object({}) do |attr, hash|
9
+ require_headers!(table.headers)
10
+
11
+ next unless deps[:check_headers]
12
+ next unless check_headers?
13
+
14
+ check_headers!(
15
+ table.headers,
16
+ order_check: options.dig(:input, :header_check, :order)
17
+ )
18
+ end.each_with_object([]) do |item, arr_hash|
19
+ item_hash = item.headers.uniq.each_with_object({}) do |attr, hash|
10
20
  next if attr.to_s.strip.empty?
11
- hash[attr.strip] = parse_string(row[attr])
21
+
22
+ hash[attr.strip] = parse_null(item[attr])
12
23
  end
13
- arr_hash.push(row_hash)
24
+
25
+ arr_hash.push(item_hash)
14
26
  end
15
27
  end
16
28
 
17
29
  def serializer(array_hash, _deps)
18
30
  arr_rows = []
31
+
19
32
  unless array_hash.empty?
20
33
  header = array_hash.first.keys
34
+
21
35
  arr_rows = array_hash.map do |csv_row|
22
36
  CSV::Row.new(header, csv_row.values_at(*header))
23
37
  end
24
38
  end
39
+
25
40
  CSV::Table.new(arr_rows).to_csv
26
41
  end
27
42
 
28
43
  private
29
44
 
30
- def abort(msg)
31
- super(msg, raising: false)
32
- end
33
-
34
- def require_headers!(table)
35
- headers = table.headers
36
-
37
- abort("Missing headers in CSV") unless headers&.any?
38
-
39
- empty = []
40
- headers.each_with_index do |header, idx|
41
- empty << idx if header.to_s.strip.empty?
42
- end
43
-
44
- abort("Empty headers in column(s): #{empty.join(', ')}") if empty.any?
45
-
46
- true
47
- end
48
-
49
45
  def check_headers?
50
46
  !options.dig(:input, :header_check, :skip)
51
47
  end
52
-
53
- def options
54
- ASSETS.cli.options
55
- end
56
-
57
- def parse_string(value)
58
- return nil if value.to_s.empty?
59
- return nil if null?(value)
60
- value
61
- end
62
-
63
- def null?(value)
64
- return true unless value
65
-
66
- str = value.strip.upcase
67
- ["NULL"].any? {|token| str == token}
68
- end
69
-
70
- def check_headers(table) # rubocop:disable Metrics/AbcSize
71
- headers = table.headers
72
- unmatch = []
73
- unmatch = unmatched_headers(headers) if options.dig(:input, :header_check, :order)
74
- missing = missing_headers(headers)
75
- unknown = unknown_headers(headers)
76
- criteria = [unknown, missing[:direct], missing[:indirect], unmatch]
77
- return if criteria.all?(&:empty?)
78
-
79
- msg = "Detected possible HEADER ISSUES !!!\n"
80
-
81
- # requires exact match
82
- unless unmatch.empty?
83
- msg << "CSV headers do NOT exactly match the expected:\n"
84
- msg << " * Expected: #{known_headers}\n"
85
- expected, given = unmatch.first
86
- msg << " * First unmatch => Given: '#{given}' where expected '#{expected}'\n"
87
- missed = known_headers - headers
88
- unless missed.empty?
89
- msg << " * Missing headers:\n"
90
- msg << " - #{missed.join("\n - ")}\n"
91
- end
92
- end
93
-
94
- msg << "Missing or Wrong HEADER names in the CSV file:\n"
95
- msg << " * UNKNOWN (or not used?): #{unknown}\n" unless unknown.empty?
96
- msg << " * MISSING HEADER: #{missing[:direct]}\n" unless missing[:direct].empty?
97
-
98
- unless (data = missing[:indirect]).empty?
99
- msg << " * MISSING INDIRECTLY:\n"
100
- data.each do |ext, info|
101
- msg << " - '#{ext}' => "
102
- msg << (info[:attrs] || {}).map do |status, attrs|
103
- if status == :inactive
104
- "makes inactive: #{attrs}"
105
- elsif status == :active
106
- "there could be missing info in: #{attrs}"
107
- end
108
- end.compact.join("; ")
109
- msg << "\n"
110
- end
111
- end
112
-
113
- log(:warn) { msg }
114
-
115
- msg = "There were issues identified on the CSV header names. Aborting..."
116
- abort(msg) if options.dig(:input, :header_check, :must_be_valid)
117
-
118
- sleep(2)
119
- end
120
-
121
- def unmatched_headers(headers)
122
- known_headers.zip(headers).reject do |(expected, given)|
123
- expected == given
124
- end
125
- end
126
-
127
- def unknown_headers(headers)
128
- (headers - known_headers) - all_internal_attrs
129
- end
130
-
131
- def missing_headers(headers) # rubocop:disable Metrics/AbcSize
132
- int_head = internal_present_or_active(headers)
133
- external = headers.select do |e|
134
- i = fields_mapper.to_internal(e)
135
- int_head.include?(i)
136
- end
137
-
138
- ext_present = known_headers_present(int_head) | external
139
- ext_miss = known_headers - ext_present
140
-
141
- {
142
- direct: [],
143
- indirect: {}
144
- }.tap do |missing|
145
- ext_miss.each do |ext|
146
- next unless (int = fields_mapper.to_internal(ext))
147
-
148
- missing[:direct] << ext if all_internal_attrs.include?(int)
149
- related_attrs_requirements = required_attrs.values.select do |req|
150
- dep = req.dependant?(int)
151
- affects = dep && !int_head.include?(int)
152
- in_header = int_head.include?(req.attr)
153
- affects || (dep && !in_header)
154
- end
155
-
156
- next if related_attrs_requirements.empty?
157
-
158
- data = missing[:indirect][ext] = {}
159
- data[:int] = int
160
- data[:attrs] = {}
161
-
162
- related_attrs_requirements.each_with_object(data[:attrs]) do |req, attrs|
163
- status = req.active?(*int_head) ? :active : :inactive
164
- attrs[status] ||= []
165
- attrs[status] << req.attr
166
- end
167
- end
168
- end
169
- end
170
-
171
- def known_headers_present(headers_internal)
172
- known_headers.select do |ext|
173
- int = fields_mapper.to_internal(ext)
174
- headers_internal.include?(int)
175
- end
176
- end
177
-
178
- # Scopes what internal attrs appear in headers as they are
179
- def internal_present_or_active(headers, inactive_requirements = {}) # rubocop:disable Metrics/AbcSize
180
- # internal attrs that are not being mapped
181
- int_all = all_internal_attrs.reject {|i| fields_mapper.external?(i)}
182
- hint = headers & int_all
183
- hext = headers - hint
184
- int_present = hint + hext.map {|e| fields_mapper.to_internal(e)}.compact
185
-
186
- update_inactive = proc do
187
- inactive_requirements.dup.each do |attr, req|
188
- next unless req.active?(*int_present)
189
-
190
- inactive_requirements.delete(attr)
191
- int_present << attr
192
- update_inactive.call
193
- end
194
- end
195
-
196
- required_attrs.each_value do |req|
197
- next if int_present.include?(req)
198
-
199
- if req.active?(*int_present)
200
- inactive_requirements.delete(req.attr)
201
- int_present << req.attr
202
- update_inactive.call
203
- else
204
- inactive_requirements[req.attr] = req
205
- end
206
- end
207
-
208
- int_present
209
- end
210
-
211
- # The csv header names as expected
212
- def known_headers
213
- @known_headers ||= fields_mapper.list(:external).compact.uniq
214
- end
215
-
216
- def fields_mapper
217
- session.fields_mapper
218
- end
219
-
220
- def required_attrs
221
- @required_attrs ||= person_parser.required_attrs.to_h {|ra| [ra.attr, ra]}
222
- end
223
-
224
- def all_internal_attrs
225
- @all_internal_attrs ||= [].tap do |int_attrs|
226
- known_int_attrs = person_parser.all_attrs(include_defined_parsers: true)
227
- known_int_attrs |= fields_mapper.list(:internal).compact
228
- int_attrs.concat(known_int_attrs)
229
- end
230
- end
231
-
232
- def person_parser
233
- session.entry_factory.person_parser
234
- end
235
48
  end
@@ -0,0 +1,206 @@
1
+ module Eco::API::Common::People
2
+ class DefaultParsers
3
+ module Helpers
4
+ module ExpectedHeaders
5
+ include Eco::Language::AuxiliarLogger
6
+
7
+ private
8
+
9
+ def require_headers!(raw_headers)
10
+ abort("Missing headers in CSV") unless raw_headers&.any?
11
+
12
+ empty = []
13
+ raw_headers.each_with_index do |header, idx|
14
+ empty << idx if header.to_s.strip.empty?
15
+ end
16
+
17
+ abort("Empty headers in column(s): #{empty.join(', ')}") if empty.any?
18
+
19
+ true
20
+ end
21
+
22
+ def check_headers!(raw_headers, order_check: false) # rubocop:disable Metrics/AbcSize
23
+ unmatch = []
24
+ unmatch = unmatched_headers(raw_headers) if order_check
25
+ missing = missing_headers(raw_headers)
26
+ unknown = unknown_headers(raw_headers)
27
+
28
+ criteria = [unknown, missing[:direct], missing[:indirect], unmatch]
29
+ return if criteria.all?(&:empty?)
30
+
31
+ msg = "Detected possible HEADER / FIELD ISSUES !!!\n"
32
+
33
+ # requires exact match
34
+ unless unmatch.empty?
35
+ msg << "File headers/fields do NOT exactly match the expected:\n"
36
+ msg << " * Expected: #{expected_headers}\n"
37
+
38
+ expected, given = unmatch.first
39
+ msg << " * First unmatch => Given: '#{given}' where expected '#{expected}'\n"
40
+
41
+ missed = expected_headers - raw_headers
42
+
43
+ unless missed.empty?
44
+ msg << " * Missing headers/fields:\n"
45
+ msg << " - #{missed.join("\n - ")}\n"
46
+ end
47
+ end
48
+
49
+ msg << "Missing or Wrong HEADER names in the file:\n"
50
+ msg << " * UNKNOWN (or not used?): #{unknown}\n" unless unknown.empty?
51
+ msg << " * MISSING HEADER/FIELD: #{missing[:direct]}\n" unless missing[:direct].empty?
52
+
53
+ unless (data = missing[:indirect]).empty?
54
+ msg << " * MISSING INDIRECTLY:\n"
55
+
56
+ data.each do |ext, info|
57
+ msg << " - '#{ext}' => "
58
+ msg << (info[:attrs] || {}).map do |status, attrs|
59
+ if status == :inactive
60
+ "makes inactive: #{attrs}"
61
+ elsif status == :active
62
+ "there could be missing info in: #{attrs}"
63
+ end
64
+ end.compact.join("; ")
65
+
66
+ msg << "\n"
67
+ end
68
+ end
69
+
70
+ log(:warn) { msg }
71
+
72
+ msg = "There were issues identified on the file header/field names. Aborting..."
73
+ abort(msg) if options.dig(:input, :header_check, :must_be_valid)
74
+
75
+ sleep(2)
76
+ end
77
+
78
+
79
+ # @return [Hash] with missing `:direct` and `:indirect` attrs, where
80
+ # - `:direct` [Array] refers to direct attrs
81
+ # - `:indirect` [Hash] refers to indirect attrs that are `:active` or `:inactive`.
82
+ def missing_headers(raw_headers) # rubocop:disable Metrics/AbcSize
83
+ int_head = internal_present_or_active(raw_headers)
84
+
85
+ external = raw_headers.select do |e|
86
+ i = fields_mapper.to_internal(e)
87
+ int_head.include?(i)
88
+ end
89
+
90
+ ext_present = present_internal_expected_headers(int_head) | external
91
+ ext_miss = expected_headers - ext_present
92
+
93
+ {
94
+ direct: [],
95
+ indirect: {}
96
+ }.tap do |missing|
97
+ ext_miss.each do |ext|
98
+ next unless (int = fields_mapper.to_internal(ext))
99
+
100
+ missing[:direct] << ext if all_internal_attrs.include?(int)
101
+
102
+ related_attrs_requirements = required_attrs.values.select do |req|
103
+ dep = req.dependant?(int)
104
+ affects = dep && !int_head.include?(int)
105
+ in_header = int_head.include?(req.attr)
106
+ affects || (dep && !in_header)
107
+ end
108
+
109
+ next if related_attrs_requirements.empty?
110
+
111
+ data = missing[:indirect][ext] = {}
112
+ data[:int] = int
113
+ data[:attrs] = {}
114
+
115
+ related_attrs_requirements.each_with_object(data[:attrs]) do |req, attrs|
116
+ status = req.active?(*int_head) ? :active : :inactive
117
+ attrs[status] ||= []
118
+ attrs[status] << req.attr
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+
125
+ # The input file header names as expected
126
+ def expected_headers
127
+ @expected_headers ||= fields_mapper.list(:external).compact.uniq
128
+ end
129
+
130
+ def present_internal_expected_headers(internal_headers)
131
+ expected_headers.select do |ext|
132
+ int = fields_mapper.to_internal(ext)
133
+ internal_headers.include?(int)
134
+ end
135
+ end
136
+
137
+ def unmatched_headers(raw_headers)
138
+ expected_headers.zip(raw_headers).reject do |(expected, given)|
139
+ expected == given
140
+ end
141
+ end
142
+
143
+ def unknown_headers(raw_headers)
144
+ (raw_headers - expected_headers) - all_internal_attrs
145
+ end
146
+
147
+ def fields_mapper
148
+ session.fields_mapper
149
+ end
150
+
151
+ def required_attrs
152
+ @required_attrs ||= person_parser.required_attrs.to_h {|ra| [ra.attr, ra]}
153
+ end
154
+
155
+ def all_internal_attrs
156
+ @all_internal_attrs ||= [].tap do |int_attrs|
157
+ known_int_attrs = person_parser.all_attrs(include_defined_parsers: true)
158
+ known_int_attrs |= fields_mapper.list(:internal).compact
159
+ int_attrs.concat(known_int_attrs)
160
+ end
161
+ end
162
+
163
+ # Scopes what internal attrs appear in headers as they are
164
+ def internal_present_or_active(raw_headers, inactive_requirements = {}) # rubocop:disable Metrics/AbcSize
165
+ # internal attrs that are not being mapped
166
+ int_all = all_internal_attrs.reject {|i| fields_mapper.external?(i)}
167
+ hint = raw_headers & int_all
168
+ hext = raw_headers - hint
169
+ int_present = hint + hext.map {|e| fields_mapper.to_internal(e)}.compact
170
+
171
+ update_inactive = proc do
172
+ inactive_requirements.dup.each do |attr, req|
173
+ next unless req.active?(*int_present)
174
+
175
+ inactive_requirements.delete(attr)
176
+ int_present << attr
177
+ update_inactive.call
178
+ end
179
+ end
180
+
181
+ required_attrs.each_value do |req|
182
+ next if int_present.include?(req)
183
+
184
+ if req.active?(*int_present)
185
+ inactive_requirements.delete(req.attr)
186
+ int_present << req.attr
187
+ update_inactive.call
188
+ else
189
+ inactive_requirements[req.attr] = req
190
+ end
191
+ end
192
+
193
+ int_present
194
+ end
195
+
196
+ def person_parser
197
+ session.entry_factory.person_parser
198
+ end
199
+
200
+ def abort(msg)
201
+ super(msg, raising: false) if defined?(super)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,36 @@
1
+ module Eco::API::Common::People
2
+ class DefaultParsers
3
+ module Helpers
4
+ module NullParsing
5
+ private
6
+
7
+ def parse_null(value)
8
+ return if null?(value)
9
+ return parse_null_on_hash(value) if value.is_a?(Hash)
10
+ return value unless value.is_a?(Array)
11
+
12
+ value.map {|val| parse_null(val)}
13
+ end
14
+
15
+ def parse_null_on_hash(value)
16
+ return value unless value.is_a?(Hash)
17
+
18
+ value.dup do |out|
19
+ value.each do |key, val|
20
+ next out.delete(key) unless (out[key] = parse_null(val))
21
+ end
22
+ end
23
+ end
24
+
25
+ def null?(value)
26
+ return true if value.nil?
27
+ return false unless value.is_a?(String)
28
+ return true if value.strip.to_s.empty?
29
+
30
+ str = value.strip.upcase
31
+ ['NULL'].any? {|token| str == token}
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'helpers/expected_headers'
2
+ require_relative 'helpers/null_parsing'
3
+
4
+ module Eco
5
+ module API
6
+ module Common
7
+ module People
8
+ class DefaultParsers
9
+ module Helpers
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,56 @@
1
+ class Eco::API::Common::People::DefaultParsers::JsonParser < Eco::API::Common::Loaders::Parser
2
+ attribute :json
3
+
4
+ include Eco::API::Common::People::DefaultParsers::Helpers::ExpectedHeaders
5
+ include Eco::API::Common::People::DefaultParsers::Helpers::NullParsing
6
+
7
+ def parser(filename, deps)
8
+ parse_json_file(filename).tap do |data|
9
+ # Don't enable header checks for now
10
+ next
11
+
12
+ puts 'Identifying unified json object keys...'
13
+ raw_headers = data.each_with_object([]) do |item, head|
14
+ head.concat(item.keys - head)
15
+ end
16
+
17
+ require_headers!(raw_headers)
18
+
19
+ next unless deps[:check_headers]
20
+ next unless check_headers?
21
+
22
+ check_headers!(
23
+ data,
24
+ order_check: false
25
+ )
26
+ end.each_with_object([]) do |item, arr_hash|
27
+ item_hash = item.keys.each_with_object({}) do |attr, hash|
28
+ next if attr.to_s.strip.empty?
29
+
30
+ hash[attr.strip] = parse_null(item[attr])
31
+ end
32
+
33
+ arr_hash.push(item_hash)
34
+ end
35
+ end
36
+
37
+ def serializer(array_hash, _deps)
38
+ array_hash.to_json
39
+ end
40
+
41
+ private
42
+
43
+ def check_headers?
44
+ !options.dig(:input, :header_check, :skip)
45
+ end
46
+
47
+ def parse_json_file(filename)
48
+ fd = File.open(filename)
49
+ JSON.load fd # rubocop:disable Security/JSONLoad
50
+ rescue JSON::ParserError => err
51
+ log(:error) { "Parsing error on file '#{filename}'" }
52
+ raise err
53
+ ensure
54
+ fd&.close
55
+ end
56
+ end