json_csv 1.0.0 → 1.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
  SHA256:
3
- metadata.gz: 7dc36a6c25ffa37eae4a51c7d373763c3958eb10341f2659acbbf70c7f1f6cdf
4
- data.tar.gz: de793cb71e127d0518cca18a1fc5c4f31d0d595df51f23abb0c79c02394b4435
3
+ metadata.gz: bec2a36913091c5283407635386cde93a1afe3f7cc1a3fa9859fe04438b64797
4
+ data.tar.gz: 894235da40a4152593f42b2f0ba9e8a0c6c593b49c2693bd0493c3c7953d52e1
5
5
  SHA512:
6
- metadata.gz: abffea2f7c780e093a052e3d558526f5e1d93c4492a36de3e3d0a1d38eaa81be4f81ccd929352bd630b6a7db4e8b9141dc76d70a0f8a4986c5a6b1b9a1cf3316
7
- data.tar.gz: d791b8465efb81add9160dc3f2d04a1772c3762c3cc88467d456820123153fb80df7c3f02dd2071ea8e4acd518e9fc87323b0d05cc77da927cd9bf871ddcdace
6
+ metadata.gz: 62f1bd549b7fbe9325889f20f9e23a1e87a1819e2899f64498fca2f993aa0bff75180fc790dbff58dd7148f041d62bfdb67322b255d4b4f53dfcef404f6aba58
7
+ data.tar.gz: 6718a850a4849a6333c2a7752861267899cb8ede27219b28ef88b6b436af7c5d5b8f9d66170d69420b7bbbef4e3d9d40e591a39ba92e8551a2107b1cdca0ec44
@@ -1,10 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'csv'
2
4
  require 'json_csv/json_to_csv'
3
5
 
4
6
  module JsonCsv
5
7
  class CsvBuilder
6
- private_class_method :new # private constructor. we don't want users to initialize this class.
7
- attr_reader :known_headers_to_indexes # map of all headers seen by this CsvBuilder, mapped to their column order indexes
8
+ # private constructor. we don't want users to initialize this class.
9
+ private_class_method :new
10
+
11
+ # map of all headers seen by this CsvBuilder, mapped to their column order indexes
12
+ attr_reader :known_headers_to_indexes
8
13
 
9
14
  def initialize(open_csv_handle)
10
15
  @known_headers_to_indexes = {}
@@ -15,7 +20,10 @@ module JsonCsv
15
20
  def add(json_hash)
16
21
  row_to_write = []
17
22
  JsonCsv.json_hash_to_flat_csv_row_hash(json_hash).each do |column_header, cell_value|
18
- known_headers_to_indexes[column_header] = known_headers_to_indexes.length unless known_headers_to_indexes.key?(column_header)
23
+ unless known_headers_to_indexes.key?(column_header)
24
+ known_headers_to_indexes[column_header] =
25
+ known_headers_to_indexes.length
26
+ end
19
27
  row_to_write[known_headers_to_indexes[column_header]] = cell_value
20
28
  end
21
29
  @open_csv_handle << row_to_write
@@ -41,13 +49,14 @@ module JsonCsv
41
49
 
42
50
  def self.original_header_indexes_to_sorted_indexes(csv_headers, column_header_comparator)
43
51
  original_headers_to_indexes = Hash[csv_headers.map.with_index { |header, index| [header, index] }]
44
- headers_to_sorted_indexes = Hash[csv_headers.sort(&column_header_comparator).map.with_index { |header, index| [header, index] }]
52
+ headers_to_sorted_indexes = Hash[csv_headers.sort(&column_header_comparator).map.with_index do |header, index|
53
+ [header, index]
54
+ end ]
45
55
  original_to_sorted_index_map = {}
46
56
  original_headers_to_indexes.each do |header, original_index|
47
57
  original_to_sorted_index_map[original_index] = headers_to_sorted_indexes[header]
48
58
  end
49
59
  original_to_sorted_index_map
50
60
  end
51
-
52
61
  end
53
62
  end
@@ -1,13 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json_csv/utils'
2
4
  require 'csv'
3
5
 
4
6
  module JsonCsv
5
7
  module CsvToJson
6
-
7
- TYPE_STRING = 'string'.freeze
8
- TYPE_INTEGER = 'integer'.freeze
9
- TYPE_FLOAT = 'float'.freeze
10
- TYPE_BOOLEAN = 'boolean'.freeze
8
+ TYPE_STRING = 'string'
9
+ TYPE_INTEGER = 'integer'
10
+ TYPE_FLOAT = 'float'
11
+ TYPE_BOOLEAN = 'boolean'
11
12
  FIELD_CASTING_TYPES = [TYPE_STRING, TYPE_INTEGER, TYPE_FLOAT, TYPE_BOOLEAN].freeze
12
13
 
13
14
  def self.included(base)
@@ -19,9 +20,14 @@ module JsonCsv
19
20
  # presenting that row as un-flattened json.
20
21
  # This method works for large CSVs and uses very little memory
21
22
  # because it only keeps one row in memory at a time.
22
- # Sample usage: csv_file_to_hierarchical_json_hash(path_to_csv, field_casting_rules = {}, strip_value_whitespace = true) do |row_json_hash, row_number|
23
+ # Sample usage:
24
+ # csv_file_to_hierarchical_json_hash(
25
+ # path_to_csv, field_casting_rules = {}, strip_value_whitespace = true
26
+ # ) { |row_json_hash, row_number| ...your block logic here... }
23
27
  def csv_file_to_hierarchical_json_hash(path_to_csv, field_casting_rules = {}, strip_value_whitespace = true)
24
- i = 2 # start with row 2 because this corresponds to the SECOND row of 1-indexed CSV data (where headers are row 1)
28
+ # Start with row 2 because this corresponds to the SECOND row of
29
+ # 1-indexed CSV data (where headers are row 1)
30
+ i = 2
25
31
  CSV.foreach(path_to_csv, headers: true, header_converters: lambda { |header|
26
32
  header.strip # remove leading and trailing header whitespace
27
33
  }) do |row_data_hash|
@@ -34,6 +40,7 @@ module JsonCsv
34
40
  hierarchical_hash = {}
35
41
  row_data_hash.each do |key, value|
36
42
  next if value.nil? || value == '' # ignore nil or empty string values
43
+
37
44
  put_value_at_json_path(hierarchical_hash, key, value, field_casting_rules)
38
45
  end
39
46
  # Clean up empty array elements, which may have come about from CSV data
@@ -53,11 +60,19 @@ module JsonCsv
53
60
  if json_path_pieces.length == 1
54
61
  # If the full_json_path_from_top matches one of the field_casting_rules,
55
62
  # then case this field to the specified cast type
56
- full_json_path_from_top_as_field_casting_rule_pattern = real_json_path_to_field_casting_rule_pattern(full_json_path_from_top)
57
- obj[json_path_pieces[0]] = field_casting_rules.key?(full_json_path_from_top_as_field_casting_rule_pattern) ? apply_field_casting_type(value, field_casting_rules[full_json_path_from_top_as_field_casting_rule_pattern]) : value
63
+ full_json_path_from_top_as_field_casting_rule_pattern =
64
+ real_json_path_to_field_casting_rule_pattern(full_json_path_from_top)
65
+ obj[json_path_pieces[0]] =
66
+ if field_casting_rules.key?(full_json_path_from_top_as_field_casting_rule_pattern)
67
+ apply_field_casting_type(value,
68
+ field_casting_rules[full_json_path_from_top_as_field_casting_rule_pattern])
69
+ else
70
+ value
71
+ end
58
72
  else
59
73
  obj[json_path_pieces[0]] ||= (json_path_pieces[1].is_a?(Integer) ? [] : {})
60
- put_value_at_json_path(obj[json_path_pieces[0]], pieces_to_json_path(json_path_pieces[1..-1]), value, field_casting_rules, full_json_path_from_top)
74
+ put_value_at_json_path(obj[json_path_pieces[0]], pieces_to_json_path(json_path_pieces[1..]), value,
75
+ field_casting_rules, full_json_path_from_top)
61
76
  end
62
77
  end
63
78
 
@@ -68,19 +83,28 @@ module JsonCsv
68
83
  end
69
84
 
70
85
  def apply_field_casting_type(value, field_casting_type)
71
- raise ArgumentError, "Invalid cast type #{field_casting_type}" unless FIELD_CASTING_TYPES.include?(field_casting_type)
86
+ unless FIELD_CASTING_TYPES.include?(field_casting_type)
87
+ raise ArgumentError,
88
+ "Invalid cast type #{field_casting_type}"
89
+ end
72
90
 
73
91
  case field_casting_type
74
92
  when TYPE_INTEGER
75
- raise ArgumentError, "\"#{value}\" is not an integer" unless value =~ /^[0-9]+$/
93
+ raise ArgumentError, "\"#{value}\" is not an integer" unless /^[0-9]+$/.match?(value.to_s)
94
+
76
95
  value.to_i
77
96
  when TYPE_FLOAT
78
- raise ArgumentError, "\"#{value}\" is not a float" unless value =~ /^[0-9]+(\.[0-9]+)*$/ || value =~ /^\.[0-9]+$/
97
+ unless value.to_s =~ /^[0-9]+(\.[0-9]+)*$/ || value.to_s =~ /^\.[0-9]+$/
98
+ raise ArgumentError,
99
+ "\"#{value}\" is not a float"
100
+ end
101
+
79
102
  value.to_f
80
103
  when TYPE_BOOLEAN
81
- if value.downcase == 'true'
104
+ case value.downcase
105
+ when 'true'
82
106
  true
83
- elsif value.downcase == 'false'
107
+ when 'false'
84
108
  false
85
109
  else
86
110
  raise ArgumentError, "\"#{value}\" is not a boolean"
@@ -123,7 +147,6 @@ module JsonCsv
123
147
  end
124
148
  json_path
125
149
  end
126
-
127
150
  end
128
151
  end
129
152
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'json_csv/csv_builder'
5
+ require 'fileutils'
3
6
 
4
7
  module JsonCsv
5
8
  module JsonToCsv
6
-
7
9
  def self.included(base)
8
10
  base.extend ClassMethods
9
11
  end
@@ -26,21 +28,20 @@ module JsonCsv
26
28
  # csv_builder.add(json_hash)
27
29
  # end
28
30
  # end
29
- def create_csv_for_json_records(csv_outfile_path, header_sort_comparator = DEFAULT_HEADER_SORT_COMPARATOR)
30
- csv_temp_outfile_path = csv_outfile_path + '.temp'
31
+ def create_csv_for_json_records(csv_outfile_path, header_sort_comparator = DEFAULT_HEADER_SORT_COMPARATOR, &block)
32
+ csv_temp_outfile_path = "#{csv_outfile_path}.temp"
31
33
 
32
34
  begin
33
35
  # Step 1: Build CSV with unsorted headers in temp file
34
- csv_headers = JsonCsv::CsvBuilder.create_csv_without_headers(csv_temp_outfile_path, 'wb') do |csv_builder|
35
- yield csv_builder
36
- end
36
+ csv_headers = JsonCsv::CsvBuilder.create_csv_without_headers(csv_temp_outfile_path, 'wb', &block)
37
37
 
38
38
  # Step 2: Sort CSV columns by header, based on column_header_comparator
39
- original_to_sorted_index_map = JsonCsv::CsvBuilder.original_header_indexes_to_sorted_indexes(csv_headers, header_sort_comparator)
39
+ original_to_sorted_index_map = JsonCsv::CsvBuilder.original_header_indexes_to_sorted_indexes(
40
+ csv_headers, header_sort_comparator
41
+ )
40
42
  CSV.open(csv_outfile_path, 'wb') do |final_csv|
41
43
  # Open temporary CSV for reading
42
44
  CSV.open(csv_temp_outfile_path, 'rb') do |temp_csv|
43
-
44
45
  # write out ordered header row
45
46
  reordered_header_row = []
46
47
  csv_headers.each_with_index do |header, index|
@@ -82,8 +83,11 @@ module JsonCsv
82
83
  if obj.is_a?(Hash)
83
84
  obj.each do |key, val|
84
85
  if key_contains_unallowed_characters?(key)
85
- raise ArgumentError, 'Cannot deal with hash keys that contain "[" or "]" or "." because these characters have special meanings in CSV headers.'
86
+ raise ArgumentError,
87
+ 'Cannot deal with hash keys that contain "[" or "]" or "." because '\
88
+ 'these characters have special meanings in CSV headers.'
86
89
  end
90
+
87
91
  path = parent_path + (parent_path.empty? ? '' : '.') + key
88
92
  flatten_hash(val, path, flat_hash_to_build)
89
93
  end
@@ -101,9 +105,9 @@ module JsonCsv
101
105
 
102
106
  def key_contains_unallowed_characters?(key)
103
107
  return true if key.index('[') || key.index(']') || key.index('.')
108
+
104
109
  false
105
110
  end
106
111
  end
107
-
108
112
  end
109
113
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JsonCsv
2
4
  module Utils
3
-
4
5
  # Returns true for empty strings, empty arrays, or empty hashes.
5
6
  # Also returns true for strings that only contain whitespace.
6
7
  # Returns false for all other values, including booleans and numbers.
@@ -8,33 +9,41 @@ module JsonCsv
8
9
  return true if value.respond_to?(:empty?) && value.empty? # empty string, empty array, or empty hash
9
10
  return true if value.is_a?(String) && value.strip.empty? # string that only contains whitespace
10
11
  return true if value.nil?
12
+
11
13
  false
12
14
  end
13
15
 
16
+ def self.hash_or_array?(obj)
17
+ obj.is_a?(Hash) || obj.is_a?(Array)
18
+ end
19
+
20
+ # Given a Hash or Array, recursively removes all blank fields.
21
+ # Note: This method will raise an ArgumentError if the supplied object
22
+ # is a frozen Hash or Array.
14
23
  def self.recursively_remove_blank_fields!(hash_or_array)
15
- return if hash_or_array.frozen? # We can't modify a frozen value, so we won't.
24
+ raise ArgumentError, 'Must supply a Hash or Array' unless hash_or_array?(hash_or_array)
25
+ raise ArgumentError, "Cannot modify frozen value: #{hash_or_array.inspect}" if hash_or_array.frozen?
16
26
 
17
- if hash_or_array.is_a?(Array)
27
+ case hash_or_array
28
+ when Array
18
29
  # Recurse through non-empty elements
19
30
  hash_or_array.each do |element|
20
- recursively_remove_blank_fields!(element) if element.is_a?(Hash) || element.is_a?(Array)
31
+ recursively_remove_blank_fields!(element) if hash_or_array?(element)
21
32
  end
22
33
 
23
34
  # Delete blank array element values on this array level (including empty object ({}) values)
24
35
  hash_or_array.delete_if do |element|
25
36
  removable_value?(element)
26
37
  end
27
- elsif hash_or_array.is_a?(Hash)
38
+ when Hash
28
39
  hash_or_array.each_value do |value|
29
- recursively_remove_blank_fields!(value) if value.is_a?(Hash) || value.is_a?(Array)
40
+ recursively_remove_blank_fields!(value) if hash_or_array?(value)
30
41
  end
31
42
 
32
43
  # Delete blank hash values on this hash level (including empty object ({}) values)
33
44
  hash_or_array.delete_if do |_key, value|
34
45
  removable_value?(value)
35
46
  end
36
- else
37
- raise ArgumentError, 'Must supply a hash or array.'
38
47
  end
39
48
 
40
49
  hash_or_array
@@ -44,21 +53,37 @@ module JsonCsv
44
53
  # modifying the object's nested child hashes or array.
45
54
  # Note: This method modifies hash values, but does not
46
55
  # modify hash keys.
47
- def self.recursively_strip_value_whitespace!(obj)
48
- if obj.is_a?(Array)
49
- obj.each do |element|
50
- recursively_strip_value_whitespace!(element)
56
+ def self.recursively_strip_value_whitespace!(obj, replace_frozen_strings_when_stripped: false)
57
+ case obj
58
+ when Array
59
+ obj.each_with_index do |element, ix|
60
+ if element.is_a?(String) && replace_frozen_strings_when_stripped
61
+ stripped_string = element.strip
62
+ obj[ix] = stripped_string if stripped_string != obj[ix]
63
+ else
64
+ recursively_strip_value_whitespace!(
65
+ element,
66
+ replace_frozen_strings_when_stripped: replace_frozen_strings_when_stripped
67
+ )
68
+ end
51
69
  end
52
- elsif obj.is_a?(Hash)
53
- obj.each_value do |value|
54
- recursively_strip_value_whitespace!(value)
70
+ when Hash
71
+ obj.each do |key, value|
72
+ if value.is_a?(String) && replace_frozen_strings_when_stripped
73
+ stripped_string = obj[key].strip
74
+ obj[key] = stripped_string if stripped_string != obj[key]
75
+ else
76
+ recursively_strip_value_whitespace!(
77
+ value,
78
+ replace_frozen_strings_when_stripped: replace_frozen_strings_when_stripped
79
+ )
80
+ end
55
81
  end
56
- elsif obj.is_a?(String)
82
+ when String
57
83
  obj.strip!
58
84
  end
59
85
 
60
86
  obj
61
87
  end
62
-
63
88
  end
64
89
  end
@@ -1,9 +1,9 @@
1
- module JsonCsv
1
+ # frozen_string_literal: true
2
2
 
3
- VERSION = '1.0.0'.freeze
3
+ module JsonCsv
4
+ VERSION = '1.1.1'
4
5
 
5
6
  def self.version
6
7
  VERSION
7
8
  end
8
-
9
9
  end
data/lib/json_csv.rb CHANGED
@@ -1,6 +1,8 @@
1
- require "json_csv/version"
2
- require "json_csv/json_to_csv"
3
- require "json_csv/csv_to_json"
1
+ # frozen_string_literal: true
2
+
3
+ require 'json_csv/version'
4
+ require 'json_csv/json_to_csv'
5
+ require 'json_csv/csv_to_json'
4
6
 
5
7
  module JsonCsv
6
8
  include JsonCsv::JsonToCsv
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json_csv'
2
4
 
3
5
  namespace :json_csv do
4
-
5
6
  begin
6
7
  # This code is in a begin/rescue block so that the Rakefile is usable
7
8
  # in an environment where RSpec is unavailable (i.e. production).
@@ -21,18 +22,18 @@ namespace :json_csv do
21
22
  task.fail_on_error = true
22
23
  end
23
24
  rescue LoadError => e
24
- puts "[Warning] Exception creating rspec rake tasks. This message can be ignored in environments that intentionally do not pull in the RSpec gem (i.e. production)."
25
+ puts '[Warning] Exception creating rspec rake tasks. This message can be ignored in '\
26
+ 'environments that intentionally do not pull in the RSpec gem (i.e. production).'
25
27
  puts e
26
28
  end
27
29
 
28
- desc "CI build"
30
+ desc 'CI build'
29
31
  task ci: ['json_csv:rubocop'] do
30
- Rake::Task["json_csv:rspec"].invoke
32
+ Rake::Task['json_csv:rspec'].invoke
31
33
  end
32
34
 
33
- desc "CI build"
35
+ desc 'CI build'
34
36
  task :ci_nocop do
35
- Rake::Task["json_csv:rspec"].invoke
37
+ Rake::Task['json_csv:rspec'].invoke
36
38
  end
37
-
38
39
  end
@@ -1,8 +1,8 @@
1
- namespace :json_csv do
1
+ # frozen_string_literal: true
2
2
 
3
- desc "Hello!"
4
- task :hello do
3
+ namespace :json_csv do
4
+ desc 'Hello!'
5
+ task hello: :environment do
5
6
  puts 'Hello!'
6
7
  end
7
-
8
8
  end
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_csv
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric O'Hanlon
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 2018-02-18 00:00:00.000000000 Z
@@ -39,33 +39,19 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.7'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rubocop
42
+ name: rubocul
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.51.0
47
+ version: 4.0.6
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.51.0
55
- - !ruby/object:Gem::Dependency
56
- name: rubocop-rspec
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 1.20.1
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 1.20.1
54
+ version: 4.0.6
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: simplecov
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -99,7 +85,7 @@ homepage: https://github.com/cul/json_csv
99
85
  licenses:
100
86
  - MIT
101
87
  metadata: {}
102
- post_install_message:
88
+ post_install_message:
103
89
  rdoc_options: []
104
90
  require_paths:
105
91
  - lib
@@ -107,15 +93,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
107
93
  requirements:
108
94
  - - ">="
109
95
  - !ruby/object:Gem::Version
110
- version: '0'
96
+ version: 2.6.0
111
97
  required_rubygems_version: !ruby/object:Gem::Requirement
112
98
  requirements:
113
99
  - - ">="
114
100
  - !ruby/object:Gem::Version
115
101
  version: '0'
116
102
  requirements: []
117
- rubygems_version: 3.0.8
118
- signing_key:
103
+ rubygems_version: 3.0.3.1
104
+ signing_key:
119
105
  specification_version: 4
120
106
  summary: A library for converting json to csv...and back!
121
107
  test_files: []