Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the option stringify_keys to #as_json #153

Merged
merged 3 commits into from
Jul 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
fabioperrella marked this conversation as resolved.
Show resolved Hide resolved
# because we want to have the least number of dependencies
module FormatParser
class HashUtils
julik marked this conversation as resolved.
Show resolved Hide resolved
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
fabioperrella marked this conversation as resolved.
Show resolved Hide resolved
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
julik marked this conversation as resolved.
Show resolved Hide resolved
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