resource-struct 0.2.0 → 0.3.2
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 +4 -4
- data/CHANGELOG.md +21 -0
- data/Gemfile.lock +2 -1
- data/README.md +43 -4
- data/bin/console +1 -1
- data/lib/resource_struct/extensions/indifferent_lookup.rb +104 -0
- data/lib/resource_struct/flex_struct.rb +51 -0
- data/lib/resource_struct/strict_struct.rb +38 -0
- data/lib/resource_struct/version.rb +1 -1
- data/lib/resource_struct.rb +9 -2
- metadata +5 -4
- data/lib/resource_struct/firm_struct.rb +0 -118
- data/lib/resource_struct/loose_struct.rb +0 -115
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a61800df441acd7f512f708b4830412edf391047be95e29e8c3e002d8684caf4
|
4
|
+
data.tar.gz: dc26ed248afa3f648cc4369225bb402eccf28d74f9770ff1a8f39de088009897
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd6e55f3a2cd859a37e6fef45485c322f83c8bcbfb6e1df97f4d69e594d77ddbff42668a07bec7438aa0483382e52d26f4ede84162be611cf98ca40b0be3b8b3
|
7
|
+
data.tar.gz: 4ff5a6edcb9dd6e07c4c3c5744d6e56aaefc14593d3acf6cd7fa9aeee25210656595d7ba926f3e0884265328604b01f5eba35ece222233f248244097813b2beb
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.3.2] - 2022-01-05
|
4
|
+
### Fix
|
5
|
+
- Support for proper marshalling of object
|
6
|
+
|
7
|
+
## [0.3.1] - 2022-01-05
|
8
|
+
### Fix
|
9
|
+
- Support nil meaning empty hash as first argument to `FlexStruct` and `StrictStruct`
|
10
|
+
|
11
|
+
## [0.3.0] - 2022-01-04
|
12
|
+
### Feature
|
13
|
+
- Support for `as_json` and `to_json`
|
14
|
+
- Support for `#[]=`, allowing modification on LooseStruct
|
15
|
+
- Support for `JSON.parse(STR, object_class: ResourceStruct::FlexStruct)`
|
16
|
+
- Refactor common code between LooseStruct and FirmStruct into `ResourceStruct::Extension::IndifferentLookup`
|
17
|
+
- No longer support wrong arity for method based access patterns
|
18
|
+
- Rename `LooseStruct` -> `FlexStruct`; `FirmStruct` -> `StrictStruct`
|
19
|
+
|
20
|
+
## [0.2.1] - 2022-01-01
|
21
|
+
### Fix
|
22
|
+
- Correct handling of #== operator on Structs with hashes
|
23
|
+
|
3
24
|
## [0.2.0] - 2022-01-01
|
4
25
|
### Changed
|
5
26
|
- Indifferent access support for input hash (support for symbol-based hashes in initializer)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,17 @@
|
|
1
|
-
#
|
1
|
+
# ResourceStruct
|
2
2
|
|
3
|
-
|
3
|
+

|
4
4
|
|
5
|
-
|
5
|
+
This is a gem for working with JSON resources from a network source with indifferent and method based access.
|
6
|
+
|
7
|
+
Instead of overriding Hash implementation, this wraps a Hash with indifferent access (by symbol or string keys).
|
8
|
+
This makes it fast at runtime, while still providing the necessary lookup method of choice.
|
9
|
+
|
10
|
+
There are two types `ResouceStruct::StrictStruct` and `ResourceStruct::FlexStruct`.
|
11
|
+
|
12
|
+
`ResourceStruct::StrictStruct` provides a way of wrapping a Hash such that accesses to invalid keys will raise an exception through the method lookup method; it also is immutable.
|
13
|
+
|
14
|
+
`ResouceStruct::FlexStruct` provides a way of wrapping a Hash such that it returns nil instead of raising an exception when the key is not present in the hash.
|
6
15
|
|
7
16
|
## Installation
|
8
17
|
|
@@ -22,7 +31,37 @@ Or install it yourself as:
|
|
22
31
|
|
23
32
|
## Usage
|
24
33
|
|
25
|
-
|
34
|
+
### StrictStruct
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
struct = ResourceStruct::StrictStruct.new({ "foo" => 1, "bar" => [{ "baz" => 2 }, 3] })
|
38
|
+
struct.foo? # => true
|
39
|
+
struct.brr? # => false
|
40
|
+
struct.foo # => 1
|
41
|
+
struct.bar # => [StrictStruct<{ "baz" => 2 }>, 3]
|
42
|
+
struct.brr # => NoMethodError
|
43
|
+
struct[:foo] # => 1
|
44
|
+
struct[:brr] # => nil
|
45
|
+
struct[:bar, 0, :baz] # => 2
|
46
|
+
struct[:bar, 0, :brr] # => nil
|
47
|
+
```
|
48
|
+
|
49
|
+
### FlexStruct
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
struct = ResourceStruct::FlexStruct.new({ "foo" => 1, "bar" => [{ "baz" => 2 }, 3] })
|
53
|
+
|
54
|
+
struct.foo? # => true
|
55
|
+
struct.brr? # => false
|
56
|
+
struct.foo # => 1
|
57
|
+
struct.bar # => [FlexStruct<{ "baz" => 2 }>, 3]
|
58
|
+
struct.brr # => nil
|
59
|
+
struct[:foo] # => 1
|
60
|
+
struct[:brr] # => nil
|
61
|
+
struct[:bar, 0, :baz] # => 2
|
62
|
+
struct[:bar, 0, :brr] # => nil
|
63
|
+
```
|
64
|
+
|
26
65
|
|
27
66
|
## Development
|
28
67
|
|
data/bin/console
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "bundler/setup"
|
5
|
-
require "resource
|
5
|
+
require "resource-struct"
|
6
6
|
|
7
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
8
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module ResourceStruct
|
6
|
+
module Extensions
|
7
|
+
#
|
8
|
+
# Common code between FirmStruct and LooseStruct
|
9
|
+
#
|
10
|
+
module IndifferentLookup
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
def_delegators :@hash, :to_h, :to_hash, :to_s, :as_json, :to_json
|
14
|
+
|
15
|
+
def initialize(hash = {})
|
16
|
+
@hash = hash || {}
|
17
|
+
@ro_struct = {}
|
18
|
+
|
19
|
+
raise ::ArgumentError, "first argument must be a Hash, found #{@hash.class.name}" unless @hash.is_a?(Hash)
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#{self.class.name}<#{@hash.inspect}>"
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(other)
|
27
|
+
other.is_a?(Hash) && ___all_keys_equal(other) ||
|
28
|
+
(other.is_a?(LooseStruct) || other.is_a?(FirmStruct)) &&
|
29
|
+
___all_keys_equal(other.instance_variable_get(:@hash))
|
30
|
+
end
|
31
|
+
|
32
|
+
def dig(key, *sub_keys)
|
33
|
+
ckey = ___convert_key(key)
|
34
|
+
|
35
|
+
result =
|
36
|
+
if @ro_struct.key?(ckey)
|
37
|
+
@ro_struct[ckey]
|
38
|
+
elsif key.is_a?(String)
|
39
|
+
@ro_struct[ckey] = ___convert_value(@hash[key] || @hash[key.to_sym])
|
40
|
+
else
|
41
|
+
@ro_struct[ckey] = ___convert_value(@hash[key] || @hash[ckey])
|
42
|
+
end
|
43
|
+
|
44
|
+
return result if sub_keys.empty?
|
45
|
+
|
46
|
+
return unless result
|
47
|
+
|
48
|
+
raise TypeError, "#{result.class.name} does not have #dig method" unless result.respond_to?(:dig)
|
49
|
+
|
50
|
+
result.dig(*sub_keys)
|
51
|
+
end
|
52
|
+
alias [] dig
|
53
|
+
|
54
|
+
def marshal_dump
|
55
|
+
{
|
56
|
+
data: @hash
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def marshal_load(obj)
|
61
|
+
@ro_struct = {}
|
62
|
+
@hash = obj[:data]
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def ___convert_value(value)
|
68
|
+
case value
|
69
|
+
when ::Array
|
70
|
+
value.map { |v| ___convert_value(v) }.freeze
|
71
|
+
when Hash
|
72
|
+
self.class.new(value)
|
73
|
+
else
|
74
|
+
value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def ___key?(key)
|
79
|
+
@hash.key?(key) || @hash.key?(___convert_key(key))
|
80
|
+
end
|
81
|
+
|
82
|
+
def ___convert_key(key)
|
83
|
+
key.is_a?(::Symbol) ? key.to_s : key
|
84
|
+
end
|
85
|
+
|
86
|
+
def ___all_keys_equal(other)
|
87
|
+
return false unless @hash.count == other.count
|
88
|
+
|
89
|
+
@hash.reduce(true) do |acc, (k, _)|
|
90
|
+
value = self[k]
|
91
|
+
if other.key?(k)
|
92
|
+
acc && value == other[k]
|
93
|
+
elsif k.is_a?(String)
|
94
|
+
ck = k.to_sym
|
95
|
+
acc && other.key?(ck) && value == other[ck]
|
96
|
+
else
|
97
|
+
ck = ___convert_key(k)
|
98
|
+
acc && other.key?(ck) && value == other[ck]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ResourceStruct
|
4
|
+
#
|
5
|
+
# FlexStruct provides a struct by which accessing undefined fields returns nil
|
6
|
+
#
|
7
|
+
# struct = FlexStruct.new({ "foo" => 1, "bar" => [{ "baz" => 2 }, 3] })
|
8
|
+
#
|
9
|
+
# struct.foo? # => true
|
10
|
+
# struct.brr? # => false
|
11
|
+
# struct.foo # => 1
|
12
|
+
# struct.bar # => [FlexStruct<{ "baz" => 2 }>, 3]
|
13
|
+
# struct.brr # => nil
|
14
|
+
# struct[:foo] # => 1
|
15
|
+
# struct[:brr] # => nil
|
16
|
+
# struct[:bar, 0, :baz] # => 2
|
17
|
+
# struct[:bar, 0, :brr] # => nil
|
18
|
+
#
|
19
|
+
class FlexStruct
|
20
|
+
include ::ResourceStruct::Extensions::IndifferentLookup
|
21
|
+
|
22
|
+
def []=(key, value)
|
23
|
+
ckey = ___convert_key(key)
|
24
|
+
@ro_struct.delete(ckey)
|
25
|
+
|
26
|
+
value = value.instance_variable_get(:@hash) if value.is_a?(FlexStruct) || value.is_a?(StrictStruct)
|
27
|
+
|
28
|
+
if @hash.key?(key)
|
29
|
+
@hash[key] = value
|
30
|
+
elsif key.is_a?(String) || key.is_a?(Symbol) && @hash.key?(key.to_sym)
|
31
|
+
@hash[key.to_sym] = value
|
32
|
+
else
|
33
|
+
@hash[key] = value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(name, *args)
|
38
|
+
args_length = args.length
|
39
|
+
return self[name] if ___key?(name) && args_length.zero?
|
40
|
+
return !!self[name[...-1]] if name.end_with?("?") && args_length.zero?
|
41
|
+
return self[name[...-1]] = args.first if name.end_with?("=") && args_length == 1
|
42
|
+
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def respond_to_missing?(_name, _include_private = false)
|
47
|
+
true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
LooseStruct = FlexStruct
|
51
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ResourceStruct
|
4
|
+
#
|
5
|
+
# StrictStruct provides a struct by which accessing undefined fields raises a MethodMissing error.
|
6
|
+
# This protects against accessing fields that are not present in API Responses.
|
7
|
+
#
|
8
|
+
# If you need to check whether a field exists in an api response, you can via name? methods.
|
9
|
+
#
|
10
|
+
# struct = StrictStruct.new({ "foo" => 1, "bar" => [{ "baz" => 2 }, 3] })
|
11
|
+
#
|
12
|
+
# struct.foo? # => true
|
13
|
+
# struct.brr? # => false
|
14
|
+
# struct.foo # => 1
|
15
|
+
# struct.bar # => [StrictStruct<{ "baz" => 2 }>, 3]
|
16
|
+
# struct.brr # => NoMethodError
|
17
|
+
# struct[:foo] # => 1
|
18
|
+
# struct[:brr] # => nil
|
19
|
+
# struct[:bar, 0, :baz] # => 2
|
20
|
+
# struct[:bar, 0, :brr] # => nil
|
21
|
+
#
|
22
|
+
class StrictStruct
|
23
|
+
include ::ResourceStruct::Extensions::IndifferentLookup
|
24
|
+
|
25
|
+
def method_missing(name, *args, &blk)
|
26
|
+
args_length = args.length
|
27
|
+
return self[name] if ___key?(name) && args_length.zero?
|
28
|
+
return !!self[name[...-1]] if name.end_with?("?") && args_length.zero?
|
29
|
+
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
def respond_to_missing?(name, include_private = false)
|
34
|
+
___key?(name) || name.end_with?("?") || super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
FirmStruct = StrictStruct
|
38
|
+
end
|
data/lib/resource_struct.rb
CHANGED
@@ -2,9 +2,16 @@
|
|
2
2
|
|
3
3
|
require_relative "resource_struct/version"
|
4
4
|
|
5
|
+
#
|
6
|
+
# ResourceStruct
|
7
|
+
#
|
8
|
+
# includes the factory method ResourceStruct.new(hash, opts)
|
9
|
+
# for building the various types of structs provided by the library.
|
10
|
+
#
|
5
11
|
module ResourceStruct
|
6
12
|
class Error < StandardError; end
|
7
13
|
end
|
8
14
|
|
9
|
-
require_relative "resource_struct/
|
10
|
-
require_relative "resource_struct/
|
15
|
+
require_relative "resource_struct/extensions/indifferent_lookup"
|
16
|
+
require_relative "resource_struct/strict_struct"
|
17
|
+
require_relative "resource_struct/flex_struct"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resource-struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Riedler
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-01-
|
11
|
+
date: 2022-01-05 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Openstruct like access without all the headaches of Hash method overrides
|
14
14
|
etc...
|
@@ -30,8 +30,9 @@ files:
|
|
30
30
|
- bin/setup
|
31
31
|
- lib/resource-struct.rb
|
32
32
|
- lib/resource_struct.rb
|
33
|
-
- lib/resource_struct/
|
34
|
-
- lib/resource_struct/
|
33
|
+
- lib/resource_struct/extensions/indifferent_lookup.rb
|
34
|
+
- lib/resource_struct/flex_struct.rb
|
35
|
+
- lib/resource_struct/strict_struct.rb
|
35
36
|
- lib/resource_struct/version.rb
|
36
37
|
homepage: https://github.com/AlexRiedler/resource-struct
|
37
38
|
licenses:
|
@@ -1,118 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ResourceStruct
|
4
|
-
#
|
5
|
-
# FirmStruct provides a struct by which accessing undefined fields raises a MethodMissing error.
|
6
|
-
# This protects against accessing fields that are not present in API Responses.
|
7
|
-
#
|
8
|
-
# If you need to check whether a field exists in an api response, you can via name? methods.
|
9
|
-
#
|
10
|
-
# struct = FirmStruct.new({ "foo" => 1, "bar" => [{ "baz" => 2 }, 3] })
|
11
|
-
#
|
12
|
-
# struct.foo? # => true
|
13
|
-
# struct.brr? # => false
|
14
|
-
# struct.foo # => 1
|
15
|
-
# struct.bar # => [FirmStruct<{ "baz" => 2 }>, 3]
|
16
|
-
# struct.brr # => NoMethodError
|
17
|
-
# struct[:foo] # => 1
|
18
|
-
# struct[:brr] # => nil
|
19
|
-
# struct[:bar, 0, :baz] # => 2
|
20
|
-
# struct[:bar, 0, :brr] # => nil
|
21
|
-
#
|
22
|
-
class FirmStruct
|
23
|
-
def initialize(hash)
|
24
|
-
raise ::ArgumentError, "first argument must be a Hash, found #{hash.class.name}" unless hash.is_a?(Hash)
|
25
|
-
|
26
|
-
@hash = hash
|
27
|
-
@ro_struct = {}
|
28
|
-
end
|
29
|
-
|
30
|
-
def method_missing(name, *args, &blk)
|
31
|
-
return self[name] if ___key?(name)
|
32
|
-
return !!self[name[...-1]] if name.end_with?("?")
|
33
|
-
|
34
|
-
super
|
35
|
-
end
|
36
|
-
|
37
|
-
def respond_to_missing?(name, include_private = false)
|
38
|
-
___key?(name) || name.end_with?("?") || super
|
39
|
-
end
|
40
|
-
|
41
|
-
def to_h
|
42
|
-
@hash.to_h
|
43
|
-
end
|
44
|
-
|
45
|
-
def to_hash
|
46
|
-
@hash.to_hash
|
47
|
-
end
|
48
|
-
|
49
|
-
def inspect
|
50
|
-
"#{self.class.name}<#{@hash.inspect}>"
|
51
|
-
end
|
52
|
-
|
53
|
-
def to_s
|
54
|
-
@hash.to_s
|
55
|
-
end
|
56
|
-
|
57
|
-
def ==(other)
|
58
|
-
other.is_a?(Hash) && ___all_keys_equal(other) ||
|
59
|
-
(other.is_a?(LooseStruct) || other.is_a?(FirmStruct)) && ___all_keys_equal(other.instance_variable_get(:@hash))
|
60
|
-
end
|
61
|
-
|
62
|
-
def dig(key, *sub_keys)
|
63
|
-
ckey = ___convert_key(key)
|
64
|
-
|
65
|
-
result =
|
66
|
-
if @ro_struct.key?(ckey)
|
67
|
-
@ro_struct[ckey]
|
68
|
-
elsif key.is_a?(String)
|
69
|
-
@ro_struct[ckey] = ___convert_value(@hash[key] || @hash[key.to_sym])
|
70
|
-
else
|
71
|
-
@ro_struct[ckey] = ___convert_value(@hash[key] || @hash[ckey])
|
72
|
-
end
|
73
|
-
|
74
|
-
return result if sub_keys.empty?
|
75
|
-
|
76
|
-
return unless result
|
77
|
-
|
78
|
-
raise TypeError, "#{result.class.name} does not have #dig method" unless result.respond_to?(:dig)
|
79
|
-
|
80
|
-
result.dig(*sub_keys)
|
81
|
-
end
|
82
|
-
alias [] dig
|
83
|
-
|
84
|
-
private
|
85
|
-
|
86
|
-
def ___convert_value(value)
|
87
|
-
case value
|
88
|
-
when ::Array
|
89
|
-
value.map { |v| ___convert_value(v) }.freeze
|
90
|
-
when Hash
|
91
|
-
self.class.new(value)
|
92
|
-
else
|
93
|
-
value
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def ___key?(key)
|
98
|
-
@hash.key?(key) || @hash.key?(___convert_key(key))
|
99
|
-
end
|
100
|
-
|
101
|
-
def ___convert_key(key)
|
102
|
-
key.is_a?(::Symbol) ? key.to_s : key
|
103
|
-
end
|
104
|
-
|
105
|
-
def ___all_keys_equal(other)
|
106
|
-
return false unless @hash.count == other.count
|
107
|
-
|
108
|
-
@hash.reduce(true) do |acc, (k, v)|
|
109
|
-
if other.key?(k)
|
110
|
-
acc && other[k] == v
|
111
|
-
else
|
112
|
-
ck = ___convert_key(k)
|
113
|
-
acc && other.key?(ck) && other[ck] == v
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
@@ -1,115 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ResourceStruct
|
4
|
-
#
|
5
|
-
# LooseStruct provides a struct by which accessing undefined fields returns nil
|
6
|
-
#
|
7
|
-
# struct = LooseStruct.new({ "foo" => 1, "bar" => [{ "baz" => 2 }, 3] })
|
8
|
-
#
|
9
|
-
# struct.foo? # => true
|
10
|
-
# struct.brr? # => false
|
11
|
-
# struct.foo # => 1
|
12
|
-
# struct.bar # => [LooseStruct<{ "baz" => 2 }>, 3]
|
13
|
-
# struct.brr # => nil
|
14
|
-
# struct[:foo] # => 1
|
15
|
-
# struct[:brr] # => nil
|
16
|
-
# struct[:bar, 0, :baz] # => 2
|
17
|
-
# struct[:bar, 0, :brr] # => nil
|
18
|
-
#
|
19
|
-
class LooseStruct
|
20
|
-
def initialize(hash)
|
21
|
-
raise ::ArgumentError, "first argument must be a Hash, found #{hash.class.name}" unless hash.is_a?(Hash)
|
22
|
-
|
23
|
-
@hash = hash
|
24
|
-
@ro_struct = {}
|
25
|
-
end
|
26
|
-
|
27
|
-
def method_missing(name, *_args)
|
28
|
-
return self[name] if ___key?(name)
|
29
|
-
return !!self[name[...-1]] if name.end_with?("?")
|
30
|
-
|
31
|
-
nil
|
32
|
-
end
|
33
|
-
|
34
|
-
def respond_to_missing?(_name, _include_private = false)
|
35
|
-
true
|
36
|
-
end
|
37
|
-
|
38
|
-
def to_h
|
39
|
-
@hash.to_h
|
40
|
-
end
|
41
|
-
|
42
|
-
def to_hash
|
43
|
-
@hash.to_hash
|
44
|
-
end
|
45
|
-
|
46
|
-
def inspect
|
47
|
-
"#{self.class.name}<#{@hash.inspect}>"
|
48
|
-
end
|
49
|
-
|
50
|
-
def to_s
|
51
|
-
@hash.to_s
|
52
|
-
end
|
53
|
-
|
54
|
-
def ==(other)
|
55
|
-
other.is_a?(Hash) && ___all_keys_equal(other) ||
|
56
|
-
(other.is_a?(LooseStruct) || other.is_a?(FirmStruct)) && ___all_keys_equal(other.instance_variable_get(:@hash))
|
57
|
-
end
|
58
|
-
|
59
|
-
def dig(key, *sub_keys)
|
60
|
-
ckey = ___convert_key(key)
|
61
|
-
|
62
|
-
result =
|
63
|
-
if @ro_struct.key?(ckey)
|
64
|
-
@ro_struct[ckey]
|
65
|
-
elsif key.is_a?(String)
|
66
|
-
@ro_struct[ckey] = ___convert_value(@hash[key] || @hash[key.to_sym])
|
67
|
-
else
|
68
|
-
@ro_struct[ckey] = ___convert_value(@hash[key] || @hash[ckey])
|
69
|
-
end
|
70
|
-
|
71
|
-
return result if sub_keys.empty?
|
72
|
-
|
73
|
-
return unless result
|
74
|
-
|
75
|
-
raise TypeError, "#{result.class.name} does not have #dig method" unless result.respond_to?(:dig)
|
76
|
-
|
77
|
-
result.dig(*sub_keys)
|
78
|
-
end
|
79
|
-
alias [] dig
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
def ___convert_value(value)
|
84
|
-
case value
|
85
|
-
when ::Array
|
86
|
-
value.map { |v| ___convert_value(v) }.freeze
|
87
|
-
when Hash
|
88
|
-
self.class.new(value)
|
89
|
-
else
|
90
|
-
value
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def ___key?(key)
|
95
|
-
@hash.key?(key) || @hash.key?(___convert_key(key))
|
96
|
-
end
|
97
|
-
|
98
|
-
def ___convert_key(key)
|
99
|
-
key.is_a?(::Symbol) ? key.to_s : key
|
100
|
-
end
|
101
|
-
|
102
|
-
def ___all_keys_equal(other)
|
103
|
-
return false unless @hash.count == other.count
|
104
|
-
|
105
|
-
@hash.reduce(true) do |acc, (k, v)|
|
106
|
-
if other.key?(k)
|
107
|
-
acc && other[k] == v
|
108
|
-
else
|
109
|
-
ck = ___convert_key(k)
|
110
|
-
acc && other.key?(ck) && other[ck] == v
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|