Skip to content

Commit

Permalink
Merge pull request #153 from WeTransfer/fix-151
Browse files Browse the repository at this point in the history
Add the option stringify_keys to #as_json
  • Loading branch information
fabioperrella committed Jul 15, 2020
2 parents 2b095bd + 0d48e5e commit 3799fc7
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 7 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.22.0
* Adds option `stringify_keys: true` to #as_json methods (fix #151)

## 0.21.1
* MPEG: Ensure parsing does not inadvertently return an Integer instead of Result|nil
* MPEG: Scan further into the MPEG file than previously (scan 32 1KB chunks)
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ img_info = FormatParser.parse(File.open("myimage.jpg", "rb"))
JSON.pretty_generate(img_info) #=> ...
```

To convert the result to a Hash or a structure suitable for JSON serialization

```ruby
img_info = FormatParser.parse(File.open("myimage.jpg", "rb"))
img_info.as_json

# it's also possible to convert all keys to string
img_info.as_json(stringify_keys: true)
```


## Creating your own parsers

See the [section on writing parsers in CONTRIBUTING.md](CONTRIBUTING.md#so-you-want-to-contribute-a-new-parser)
Expand Down Expand Up @@ -188,7 +199,7 @@ Unless specified otherwise in this section the fixture files are MIT licensed an

## Copyright

Copyright (c) 2019 WeTransfer.
Copyright (c) 2020 WeTransfer.

`format_parser` is distributed under the conditions of the [Hippocratic License](https://firstdonoharm.dev/version/1/2/license.html)
- See LICENSE.txt for further details.
10 changes: 9 additions & 1 deletion lib/attributes_json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ module FormatParser::AttributesJSON

# Implements a sane default `as_json` for an object
# that accessors defined
def as_json(root: false)
#
# @param root[Bool] if true, it surrounds the result in a hash with a key
# `format_parser_file_info`
# @param stringify_keys[Bool] if true, it transforms all the hash keys to a string.
# The default value is false for backward compatibility
def as_json(root: false, stringify_keys: false, **)
h = {}
h['nature'] = nature if respond_to?(:nature) # Needed for file info structs
methods.grep(/\w\=$/).each_with_object(h) do |attr_writer_method_name, h|
Expand All @@ -27,6 +32,9 @@ def as_json(root: false)
sanitized_value = _sanitize_json_value(unwrapped_attribute_value)
h[reader_method_name] = sanitized_value
end

h = FormatParser::HashUtils.deep_transform_keys(h, &:to_s) if stringify_keys

if root
{'format_parser_file_info' => h}
else
Expand Down
1 change: 1 addition & 0 deletions lib/format_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# top-level methods of the library.
module FormatParser
require_relative 'format_parser/version'
require_relative 'hash_utils'
require_relative 'attributes_json'
require_relative 'image'
require_relative 'audio'
Expand Down
2 changes: 1 addition & 1 deletion lib/format_parser/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module FormatParser
VERSION = '0.21.1'
VERSION = '0.22.0'
end
19 changes: 19 additions & 0 deletions lib/hash_utils.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# based on https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/keys.rb#L116
# I chose to copy this method instead of adding activesupport as a dependency
# because we want to have the least number of dependencies
module FormatParser
class HashUtils
def self.deep_transform_keys(object, &block)
case object
when Hash
object.each_with_object({}) do |(key, value), result|
result[yield(key)] = deep_transform_keys(value, &block)
end
when Array
object.map { |e| deep_transform_keys(e, &block) }
else
object
end
end
end
end
4 changes: 0 additions & 4 deletions lib/parsers/mp3_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ def to_h
h[k] = value if value
end
end

def as_json(*)
to_h
end
end

def likely_match?(filename)
Expand Down
26 changes: 26 additions & 0 deletions spec/attributes_json_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,30 @@ def nature
JSON.pretty_generate(object_with_attributes_module)
}.to raise_error(/structure too deep/)
end

it 'converts all hash keys to string when stringify_keys: true' do
fixture_path = fixtures_dir + '/ZIP/arch_few_entries.zip'
fi_io = File.open(fixture_path, 'rb')

result = FormatParser::ZIPParser.new.call(fi_io).as_json(stringify_keys: true)

result['entries'].each do |entry|
entry.each do |key, _value|
expect(key).to be_a(String)
end
end
end

it 'does not convert hash keys to string when stringify_keys: false' do
fixture_path = fixtures_dir + '/ZIP/arch_few_entries.zip'
fi_io = File.open(fixture_path, 'rb')

result = FormatParser::ZIPParser.new.call(fi_io).as_json

result['entries'].each do |entry|
entry.each do |key, _value|
expect(key).to be_a(Symbol)
end
end
end
end
42 changes: 42 additions & 0 deletions spec/hash_utils_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
require 'spec_helper'

describe FormatParser::HashUtils do
describe '.deep_transform_keys' do
it 'transforms all the keys in a hash' do
hash = { aa: 1, 'bb' => 2 }
result = described_class.deep_transform_keys(hash, &:to_s)

expect(result).to eq('aa' => 1, 'bb' => 2)
end

it 'transforms all the keys in a array of hashes' do
array = [{ aa: 1, bb: 2 }, { cc: 3, dd: [{c: 2, d: 3}] }]
result = described_class.deep_transform_keys(array, &:to_s)

expect(result).to eq(
[{'aa' => 1, 'bb' => 2}, {'cc' => 3, 'dd' => [{'c' => 2, 'd' => 3}]}]
)
end

it 'transforms all the keys in a hash recursively' do
hash = { aa: 1, bb: { cc: 22, dd: 3 } }
result = described_class.deep_transform_keys(hash, &:to_s)

expect(result).to eq('aa' => 1, 'bb' => { 'cc' => 22, 'dd' => 3})
end

it 'does nothing for an non array/hash object' do
object = Object.new
result = described_class.deep_transform_keys(object, &:to_s)

expect(result).to eq(object)
end

it 'returns the last value if different keys are transformed into the same one' do
hash = { aa: 0, 'bb' => 2, bb: 1 }
result = described_class.deep_transform_keys(hash, &:to_s)

expect(result).to eq('aa' => 0, 'bb' => 1)
end
end
end
28 changes: 28 additions & 0 deletions spec/parsers/mp3_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,32 @@
subject.call(StringIO.new(''))
}.to raise_error(FormatParser::IOUtils::InvalidRead)
end

describe '#as_json' do
it 'converts all hash keys to string when stringify_keys: true' do
fpath = fixtures_dir + '/MP3/Cassy.mp3'
result = subject.call(File.open(fpath, 'rb')).as_json(stringify_keys: true)

expect(
result['intrinsics'].keys.map(&:class).uniq
).to eq([String])

expect(
result['intrinsics']['id3tags'].map(&:class).uniq
).to eq([ID3Tag::Tag])
end

it 'does not convert the hash keys to string when stringify_keys: false' do
fpath = fixtures_dir + '/MP3/Cassy.mp3'
result = subject.call(File.open(fpath, 'rb')).as_json

expect(
result['intrinsics'].keys.map(&:class).uniq
).to eq([Symbol])

expect(
result['intrinsics'][:id3tags].map(&:class).uniq
).to eq([ID3Tag::Tag])
end
end
end

0 comments on commit 3799fc7

Please sign in to comment.