json_csv 1.0.0 → 1.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
  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: []