ishin 0.3.0 → 0.4.0

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
  SHA1:
3
- metadata.gz: 916893740198856f3e8461ad709f9764bf1d286e
4
- data.tar.gz: 6fdf9f93f7300115d3f6423e5784a48a39e3a366
3
+ metadata.gz: aed7e81440ced692b2e59a4b78c09953ca67c087
4
+ data.tar.gz: 97aa7d2105d7cf9288c655f32ff7595812b60ee5
5
5
  SHA512:
6
- metadata.gz: bad9f801b6189842d1a1bc57627de382d3fe22ed5847230e082d4a12ff40a289b72198ac0f8fdb59b079fbe9caef3cf5aa6b89819b0695149a08adc247d43184
7
- data.tar.gz: a4412fccee7db64485611fca3ef02182985101ca635ec935bab2af393972e78e0ac8624bf0732184d05948280380e3d3b55f96d866dac7c7f510f1de0ede2fcf
6
+ metadata.gz: e2b8de58b950995ca73c59fc4238a017346280f47ae84ee2e8c645b7b6d5ee9c49dd628d70f099bc7a385add3fe2d079bea692ab5c9afa596410bb037883a94f
7
+ data.tar.gz: 125a4421ca4a9c4102f0a50ad2ec00e4c2398534500af87a6a89b3b2ee463e0f459962584db9efd62d1ab6f5a88aacc6d0ff88ebff4167f9b1ee3314a9430424
@@ -0,0 +1,6 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'spec/**/*'
4
+ Style/ClassVars:
5
+ Exclude:
6
+ - 'spec/spec_helper.rb'
data/README.md CHANGED
@@ -5,7 +5,9 @@
5
5
 
6
6
  # Ishin
7
7
 
8
- Ishin converts Ruby objects into their Hash representations. It works with plain old classes, extended classes, classes with mixins, and hashes (see Usage for more on that).
8
+ Ishin converts Ruby objects into their Hash representations. It works with plain
9
+ old classes, extended classes, classes with mixins, and hashes (see Usage for
10
+ more information).
9
11
 
10
12
  ## Installation
11
13
 
@@ -23,6 +25,12 @@ Or install it manually by:
23
25
 
24
26
  gem install ishin
25
27
 
28
+ ## Requirements
29
+
30
+ Ishin does not require any other gems to run. There are a few gems used during
31
+ development only, but don't affect runtime performance. For details, see
32
+ `ishin.gemspec` in the root directory of the project.
33
+
26
34
  ## Example
27
35
 
28
36
  As a class method callable on any object:
@@ -66,7 +74,9 @@ dog_hash = Ishin.to_hash(dog)
66
74
 
67
75
  ### Recursion
68
76
 
69
- Ishin also handles object instances nested within other object instances. By default, recursive hash conversion is turned off. To enable recursion, set the `recursive` option to `true`:
77
+ Ishin also handles object instances nested within other object instances. By
78
+ default, recursive hash conversion is turned off. To enable recursion, set the
79
+ `recursive` option to `true`:
70
80
 
71
81
  ```ruby
72
82
  test_struct = Struct.new(:value)
@@ -79,7 +89,9 @@ Ishin.to_hash(nested_structs, recursive: true)
79
89
 
80
90
  ### Recursion Depth
81
91
 
82
- For deeply nested object instances, a maximum recursion depth can be provided in combination with the `recursive` option. The default recursion depth is one (initial call + 1).
92
+ For deeply nested object instances, a maximum recursion depth can be provided in
93
+ combination with the `recursive` option. The default recursion depth is one
94
+ (initial call + 1).
83
95
 
84
96
  ```ruby
85
97
  nest_me = Struct.new(:value)
@@ -89,11 +101,18 @@ Ishin.to_hash(deep_nesting, recursive: true, recursion_depth: 2)
89
101
  # => {:value=>{:value=>{:value=>#<struct value="such depth">}}}
90
102
  ```
91
103
 
92
- Notice in the above example that the recursion stopped after 3 steps (initial call + 2).
104
+ Notice in the above example that the recursion stopped after 3 steps (initial
105
+ call + 2).
106
+
107
+ **Warning:** Increasing the recursion depth will affect the runtime of the
108
+ conversion process significantly. To see how drastic increased recursion levels
109
+ affect performance, run `ruby benchmarks/recursive_bench.rb`
93
110
 
94
111
  ### Expanding Hashes using Recursion
95
112
 
96
- Using recursion, it is also possible to convert hashes containing object instances to a hash-only representation as well. Notice that this only works if the `recursive` option is provided.
113
+ Using recursion, it is also possible to convert hashes containing object
114
+ instances to a hash-only representation as well. Notice that this only works if
115
+ the `recursive` option is provided.
97
116
 
98
117
  ```ruby
99
118
  another_struct = Struct.new(:value)
@@ -105,11 +124,13 @@ Ishin.to_hash(my_hash, recursive: true)
105
124
  # => {:my_struct=>{:value=>"yup, it's a struct"}}
106
125
  ```
107
126
 
108
- Keep in mind that the `recursive` option works in conjunction with the `recursion_depth` option.
127
+ Keep in mind that the `recursive` option works in conjunction with the
128
+ `recursion_depth` option.
109
129
 
110
130
  ### Symbolizing Keys
111
131
 
112
- By default, Ishin stores key names as symbols. This behavior can be disabled by setting the `symbolize` option to `false`.
132
+ By default, Ishin stores key names as symbols. This behavior can be disabled by
133
+ setting the `symbolize` option to `false`.
113
134
 
114
135
  ```ruby
115
136
  class Dog
@@ -127,11 +148,14 @@ Ishin.to_hash(lassie)
127
148
  Ishin.to_hash(lassie, symbolize: false)
128
149
  # => {"says"=>"Timmy is stuck in a well!"}
129
150
  ```
130
- When setting the `symbolize` option to `false`, the explicit conversion of strings to symbols is prevented. This, however, does *not* mean that hashes whose keys are already symbols are converted into string-based keys.
151
+ When setting the `symbolize` option to `false`, the explicit conversion of
152
+ strings to symbols is prevented. This, however, does *not* mean that hashes
153
+ whose keys are already symbols are converted into string-based keys.
131
154
 
132
155
  ### Evaluating Methods
133
156
 
134
- Normally, Ishin does not evaluate methods, but is possible to do so through the optional `evaluate` option by passing it an array of method names to evaluate.
157
+ Normally, Ishin does not evaluate methods, but is possible to do so through the
158
+ optional `evaluate` option by passing it an array of method names to evaluate.
135
159
 
136
160
  ```ruby
137
161
  class Speaker
@@ -159,7 +183,15 @@ class MyObject
159
183
  end
160
184
  ```
161
185
 
162
- Your object now exposes a method named `to_hash` taking the same options at the `Ishin::to_hash` class method documented above.
186
+ Your object now exposes a method named `to_hash` taking the same options at the
187
+ `Ishin::to_hash` class method documented above.
188
+
189
+ ## Running Code Quality Tools
190
+
191
+ To run the code quality tools Rubocop, Reek, and RSpec, run the following
192
+ command:
193
+
194
+ rake
163
195
 
164
196
  ## Running the Specs
165
197
 
@@ -167,6 +199,14 @@ Once `bundle` is executed, simply run:
167
199
 
168
200
  rake spec
169
201
 
202
+ ## Running the Benchmarks
203
+
204
+ To give an idea on what kind of performance can be expected from Ishin, there
205
+ are a few IPS (iterations per second) benchmarks located in the `benchmarks`
206
+ directory. These can all be executed in series by running:
207
+
208
+ rake bench
209
+
170
210
  ## Changelog
171
211
 
172
212
  * 0.1.0
@@ -175,3 +215,7 @@ Once `bundle` is executed, simply run:
175
215
  * Added `Ishin::Mixin`.
176
216
  * 0.2.1
177
217
  * Now using `Struct.to_h` when converting a `Struct` instance.
218
+ * 0.3.0
219
+ * Added support for evaluating object methods.
220
+ * 0.4.0
221
+ * Cleanup: preparing an overhaul
data/Rakefile CHANGED
@@ -1,6 +1,20 @@
1
1
  require 'rspec/core/rake_task'
2
2
  require 'bundler/gem_tasks'
3
+ require 'rubocop/rake_task'
4
+ require 'reek/rake/task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
7
+ RuboCop::RakeTask.new
8
+ Reek::Rake::Task.new
5
9
 
6
- task :default => :spec
10
+ task :bench do
11
+ sh 'ruby ./benchmarks/simple_bench.rb'
12
+ sh 'ruby ./benchmarks/recursive_bench.rb'
13
+ sh 'ruby ./benchmarks/symbolize_bench.rb'
14
+ end
15
+
16
+ task :default do
17
+ Rake::Task[:rubocop].invoke
18
+ Rake::Task[:spec].invoke
19
+ Rake::Task[:reek].invoke
20
+ end
@@ -0,0 +1,39 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'benchmark/ips'
3
+ require 'ishin'
4
+
5
+ # A dummy class used for simply holding some data
6
+ class Test
7
+ attr_reader :value
8
+ def initialize(value)
9
+ @value = value
10
+ end
11
+ end
12
+
13
+ Benchmark.ips do |x|
14
+ x.config(time: 5, warmup: 2)
15
+
16
+ test_object = Test.new(Test.new(Test.new(Test.new('value'))))
17
+
18
+ one_level_options = { recursive: true, recursion_depth: 2 }
19
+ two_level_options = { recursive: true, recursion_depth: 3 }
20
+ three_level_options = { recursive: true, recursion_depth: 4 }
21
+
22
+ x.report('no recursion') do
23
+ Ishin.to_hash(test_object)
24
+ end
25
+
26
+ x.report('one level of recursion') do
27
+ Ishin.to_hash(test_object, one_level_options)
28
+ end
29
+
30
+ x.report('two levels of recursion') do
31
+ Ishin.to_hash(test_object, two_level_options)
32
+ end
33
+
34
+ x.report('three levels of recursion') do
35
+ Ishin.to_hash(test_object, three_level_options)
36
+ end
37
+
38
+ x.compare!
39
+ end
@@ -0,0 +1,37 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'benchmark/ips'
3
+ require 'ishin'
4
+
5
+ # Non-mixin version to be used with .to_hash
6
+ class Simple
7
+ attr_reader :test
8
+
9
+ def initialize
10
+ @test = rand
11
+ end
12
+ end
13
+
14
+ # Mixed-in version
15
+ class MixedIn
16
+ include Ishin::Mixin
17
+
18
+ attr_reader :test
19
+
20
+ def initialize
21
+ @test = rand
22
+ end
23
+ end
24
+
25
+ Benchmark.ips do |x|
26
+ x.config(time: 5, warmup: 2)
27
+
28
+ x.report('.to_hash class method') do
29
+ Ishin.to_hash(Simple.new)
30
+ end
31
+
32
+ x.report('.to_hash mixin method') do
33
+ MixedIn.new.to_hash
34
+ end
35
+
36
+ x.compare!
37
+ end
@@ -0,0 +1,26 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'benchmark/ips'
3
+ require 'ishin'
4
+
5
+ # Non-mixin version to be used with .to_hash
6
+ class Simple
7
+ attr_reader :test
8
+
9
+ def initialize
10
+ @test = rand
11
+ end
12
+ end
13
+
14
+ Benchmark.ips do |x|
15
+ x.config(time: 5, warmup: 2)
16
+
17
+ x.report('symbolize: true') do
18
+ Ishin.to_hash(Simple.new, symbolize: true)
19
+ end
20
+
21
+ x.report('symbolize: false') do
22
+ Ishin.to_hash(Simple.new, symbolize: false)
23
+ end
24
+
25
+ x.compare!
26
+ end
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "ishin"
3
+ require 'bundler/setup'
4
+ require 'ishin'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
8
8
 
9
9
  # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
10
+ # require 'pry'
11
11
  # Pry.start
12
12
 
13
- require "irb"
13
+ require 'irb'
14
14
  IRB.start
@@ -5,22 +5,27 @@ require 'ishin/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'ishin'
8
- spec.version = Ishin::VERSION
8
+ spec.version = Ishin::Version::STRING
9
9
  spec.authors = ['Eddy Luten']
10
10
  spec.email = ['eddyluten@gmail.com']
11
11
 
12
- spec.summary = %q{Ishin is an object to hash converter.}
13
- spec.description = %q{Ishin converts objects into their Hash representations.}
12
+ spec.summary = 'Ishin is an object to hash converter.'
13
+ spec.description = 'Ishin converts objects into their Hash representations.'
14
14
  spec.homepage = 'https://github.com/EddyLuten/ishin'
15
15
  spec.license = 'MIT'
16
16
  spec.platform = Gem::Platform::RUBY
17
17
 
18
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |file|
19
+ file.match(%r{^(test|spec|features)/})
20
+ end
19
21
  spec.bindir = 'exe'
20
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
23
  spec.require_paths = ['lib']
22
24
 
23
- spec.add_development_dependency 'rake', '~> 10.0'
24
- spec.add_development_dependency 'rspec'
25
- spec.add_development_dependency 'codeclimate-test-reporter'
25
+ spec.add_development_dependency 'benchmark-ips', '~> 2.3'
26
+ spec.add_development_dependency 'codeclimate-test-reporter', '~> 0.4.8'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'reek', '~> 3.7'
29
+ spec.add_development_dependency 'rspec', '~> 3.4'
30
+ spec.add_development_dependency 'rubocop', '~> 0.35.1'
26
31
  end
@@ -1,28 +1,29 @@
1
1
  require 'ishin/version'
2
+ require 'ishin/mixin'
2
3
 
4
+ # Ishin is an object to hash converter Ruby Gem
3
5
  module Ishin
4
- module Mixin
5
- def to_hash(options = {})
6
- Ishin.to_hash(self, options)
7
- end
8
- end
9
-
6
+ # Converts objects into their hash representations.
7
+ # @param object [Object] an object to convert
8
+ # @param options [Hash] a hash containing conversion options
10
9
  def self.to_hash(object, options = {})
11
10
  options = defaults.merge(options)
12
11
  result = {}
12
+ type_to_hash(result, object, options)
13
+ evaluate_methods(result, object, options)
14
+ result
15
+ end
13
16
 
17
+ private
18
+
19
+ def self.type_to_hash(result, object, options)
14
20
  case object
15
21
  when Struct then struct_to_hash(result, object, options)
16
22
  when Hash then hash_to_hash(result, object, options)
17
23
  else object_to_hash(result, object, options)
18
24
  end
19
-
20
- evaluate_methods(result, object, options)
21
- result
22
25
  end
23
26
 
24
- private
25
-
26
27
  def self.decrement_recursion_depth(options)
27
28
  options[:recursion_depth] = [0, options[:recursion_depth] - 1].max
28
29
  options
@@ -36,12 +37,18 @@ module Ishin
36
37
  new_options = decrement_recursion_depth(options.clone)
37
38
 
38
39
  object.each do |key, value|
39
- key = key.to_sym if options[:symbolize] && key.is_a?(String)
40
-
41
- result[key] = native_type?(value) ? value : to_hash(value, new_options)
40
+ result[hash_key(key, options)] = hash_value(value, new_options)
42
41
  end
43
42
  end
44
43
 
44
+ def self.hash_key(key, options)
45
+ options[:symbolize] && key.is_a?(String) ? key.to_sym : key
46
+ end
47
+
48
+ def self.hash_value(value, new_options)
49
+ native_type?(value) ? value : to_hash(value, new_options)
50
+ end
51
+
45
52
  def self.struct_to_hash(result, object, options)
46
53
  result.replace(object.to_h)
47
54
  return unless should_recurse?(options)
@@ -58,33 +65,45 @@ module Ishin
58
65
 
59
66
  object.instance_variables.each do |var|
60
67
  value = object.instance_variable_get(var)
61
- key = var.to_s.delete('@')
62
- key = key.to_sym if options[:symbolize]
68
+ key = instance_variable_to_key(var, options)
69
+ result[key] = object_value(value, options, new_options)
70
+ end
71
+ end
63
72
 
64
- if should_recurse?(options) && !native_type?(value)
65
- result[key] = to_hash(value, new_options)
66
- else
67
- result[key] = value
68
- end
73
+ def self.instance_variable_to_key(instance_variable, options)
74
+ key = instance_variable.to_s.delete('@')
75
+ options[:symbolize] ? key.to_sym : key
76
+ end
77
+
78
+ def self.object_value(value, options, new_options)
79
+ if should_recurse?(options) && !native_type?(value)
80
+ to_hash(value, new_options)
81
+ else
82
+ value
69
83
  end
70
84
  end
71
85
 
72
86
  def self.evaluate_methods(result, object, options)
73
- return if options[:evaluate].nil? || options[:evaluate].empty?
87
+ evaluate = options[:evaluate]
88
+ return if !evaluate || evaluate.empty?
74
89
 
75
- options[:evaluate].each do |method|
76
- next unless object.methods.include?(method)
77
- key_name = options[:symbolize] ? method : method.to_s
78
- result[key_name] = object.send(method)
90
+ evaluate.each do |method|
91
+ if object.methods.include?(method)
92
+ result[method_name_to_key(method, options)] = object.send(method)
93
+ end
79
94
  end
80
95
  end
81
96
 
97
+ def self.method_name_to_key(method, options)
98
+ options[:symbolize] ? method : method.to_s
99
+ end
100
+
82
101
  def self.should_recurse?(options)
83
102
  options[:recursive] && options[:recursion_depth] > 0
84
103
  end
85
104
 
86
105
  def self.native_type?(value)
87
- [String, Numeric, TrueClass, FalseClass].any? { |i| value.is_a?(i) }
106
+ [String, Numeric, TrueClass, FalseClass].any? { |type| value.is_a?(type) }
88
107
  end
89
108
 
90
109
  def self.defaults
@@ -0,0 +1,12 @@
1
+ module Ishin
2
+ # Can be used to include the Ishin functionality into your class and provides
3
+ # a `.to_hash` method.
4
+ module Mixin
5
+ # Calls `Ishin.to_hash` and passes the parameters on.
6
+ # @param options [Hash]
7
+ # @see Ishin.to_hash
8
+ def to_hash(options = {})
9
+ Ishin.to_hash(self, options)
10
+ end
11
+ end
12
+ end
@@ -1,3 +1,10 @@
1
1
  module Ishin
2
- VERSION = '0.3.0'
2
+ # Contains the version number for the gem.
3
+ module Version
4
+ MAJOR = '0'.freeze
5
+ MINOR = '4'.freeze
6
+ PATCH = '0'.freeze
7
+
8
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
9
+ end
3
10
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ishin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eddy Luten
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-08-14 00:00:00.000000000 Z
11
+ date: 2015-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: benchmark-ips
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: codeclimate-test-reporter
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.4.8
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.4.8
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: rake
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -24,34 +52,48 @@ dependencies:
24
52
  - - "~>"
25
53
  - !ruby/object:Gem::Version
26
54
  version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: reek
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.7'
27
69
  - !ruby/object:Gem::Dependency
28
70
  name: rspec
29
71
  requirement: !ruby/object:Gem::Requirement
30
72
  requirements:
31
- - - ">="
73
+ - - "~>"
32
74
  - !ruby/object:Gem::Version
33
- version: '0'
75
+ version: '3.4'
34
76
  type: :development
35
77
  prerelease: false
36
78
  version_requirements: !ruby/object:Gem::Requirement
37
79
  requirements:
38
- - - ">="
80
+ - - "~>"
39
81
  - !ruby/object:Gem::Version
40
- version: '0'
82
+ version: '3.4'
41
83
  - !ruby/object:Gem::Dependency
42
- name: codeclimate-test-reporter
84
+ name: rubocop
43
85
  requirement: !ruby/object:Gem::Requirement
44
86
  requirements:
45
- - - ">="
87
+ - - "~>"
46
88
  - !ruby/object:Gem::Version
47
- version: '0'
89
+ version: 0.35.1
48
90
  type: :development
49
91
  prerelease: false
50
92
  version_requirements: !ruby/object:Gem::Requirement
51
93
  requirements:
52
- - - ">="
94
+ - - "~>"
53
95
  - !ruby/object:Gem::Version
54
- version: '0'
96
+ version: 0.35.1
55
97
  description: Ishin converts objects into their Hash representations.
56
98
  email:
57
99
  - eddyluten@gmail.com
@@ -61,14 +103,19 @@ extra_rdoc_files: []
61
103
  files:
62
104
  - ".gitignore"
63
105
  - ".rspec"
106
+ - ".rubocop.yml"
64
107
  - ".travis.yml"
65
108
  - Gemfile
66
109
  - LICENSE.txt
67
110
  - README.md
68
111
  - Rakefile
112
+ - benchmarks/recursive_bench.rb
113
+ - benchmarks/simple_bench.rb
114
+ - benchmarks/symbolize_bench.rb
69
115
  - bin/console
70
116
  - ishin.gemspec
71
117
  - lib/ishin.rb
118
+ - lib/ishin/mixin.rb
72
119
  - lib/ishin/version.rb
73
120
  homepage: https://github.com/EddyLuten/ishin
74
121
  licenses:
@@ -90,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
137
  version: '0'
91
138
  requirements: []
92
139
  rubyforge_project:
93
- rubygems_version: 2.4.5
140
+ rubygems_version: 2.4.5.1
94
141
  signing_key:
95
142
  specification_version: 4
96
143
  summary: Ishin is an object to hash converter.