From 0bf5536f03cb9b217ca79b1b45b96b003a56fd7e Mon Sep 17 00:00:00 2001
From: "Peter H. Boling"
Date: Thu, 22 May 2025 01:21:29 +0700
Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8=20Serialization=20Extensions?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 91 ++++++++++++++-
doc/SnakyHash.html | 2 +-
doc/SnakyHash/Error.html | 2 +-
doc/SnakyHash/Extensions.html | 2 +-
doc/SnakyHash/Serializer.html | 4 +-
.../Serializer/BackportedInstanceMethods.html | 2 +-
doc/SnakyHash/Serializer/Modulizer.html | 2 +-
doc/SnakyHash/Snake.html | 2 +-
doc/SnakyHash/Snake/SnakyModulizer.html | 2 +-
doc/SnakyHash/StringKeyed.html | 2 +-
doc/SnakyHash/SymbolKeyed.html | 2 +-
doc/SnakyHash/Version.html | 2 +-
doc/_index.html | 2 +-
doc/file.CHANGELOG.html | 2 +-
doc/file.CODE_OF_CONDUCT.html | 2 +-
doc/file.CONTRIBUTING.html | 2 +-
doc/file.LICENSE.html | 2 +-
doc/file.README.html | 107 +++++++++++++++++-
doc/file.SECURITY.html | 2 +-
doc/index.html | 107 +++++++++++++++++-
doc/top-level-namespace.html | 2 +-
lib/snaky_hash/serializer.rb | 4 +-
spec/shared_examples/a_serialized_hash.rb | 2 +-
23 files changed, 317 insertions(+), 32 deletions(-)
diff --git a/README.md b/README.md
index b6bb84b..6d85a10 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails.
-This gem is used by `oauth`, `oauth2`, and other, gems to normalize hash keys to `snake_case` and lookups,
+This gem is used by `oauth` and `oauth2` gems to normalize hash keys to `snake_case` and lookups,
and provide a nice psuedo-object interface.
It can be thought of as a mashup, with upgrades, to the `Rash` (specifically the [`rash_alt`](https://github.com/shishi/rash_alt) flavor), which is a special `Mash`, made popular by the `hashie` gem, and the `serialized_hashie` [gem by krystal](https://github.com/krystal/serialized-hashie).
@@ -147,7 +147,8 @@ end
snake = MySnakedHash.new(:a => "a", "b" => "b", 2 => 2, "VeryFineHat" => "Feathers")
snake.a # => 'a'
snake.b # => 'b'
-snake[2] # 2
+snake[2] # => 2
+snake["2"] # => nil, note that this gem only affects string / symbol keys.
snake.very_fine_hat # => 'Feathers'
snake[:very_fine_hat] # => 'Feathers'
snake["very_fine_hat"] # => 'Feathers'
@@ -158,11 +159,97 @@ The `key_type` determines how the key is actually stored, but the hash acts as "
Note also that keys which do not respond to `to_sym`, because they don't have a natural conversion to a Symbol,
are left as-is.
+### Serialization
+
+```ruby
+class MySerializedSnakedHash < Hashie::Mash
+ include SnakyHash::Snake.new(
+ key_type: :symbol, # default :string
+ serializer: true, # default: false
+ )
+end
+
+snake = MySerializedSnakedHash.new(:a => "a", "b" => "b", 2 => 2, "VeryFineHat" => "Feathers") # => {a: "a", b: "b", 2 => 2, very_fine_hat: "Feathers"}
+dump = MySerializedSnakedHash.dump(snake) # => "{\"a\":\"a\",\"b\":\"b\",\"2\":2,\"very_fine_hat\":\"Feathers\"}"
+hydrated = MySerializedSnakedHash.load(dump) # => {a: "a", b: "b", "2": 2, very_fine_hat: "Feathers"}
+hydrated.class # => MySerializedSnakedHash
+hydrated.a # => 'a'
+hydrated.b # => 'b'
+hydrated[2] # => nil # NOTE: this is the opposite of snake[2] => 2
+hydrated["2"] # => 2 # NOTE: this is the opposite of snake["2"] => nil
+hydrated.very_fine_hat # => 'Feathers'
+hydrated[:very_fine_hat] # => 'Feathers'
+hydrated["very_fine_hat"] # => 'Feathers'
+```
+
+Note that the key `VeryFineHat` changed to `very_fine_hat`.
+That is indeed the point of this library, so not a bug.
+
+Note that the key `2` changed to `"2"` (because JSON keys are strings).
+When the JSON dump was reloaded it did not know to restore it as `2` instead of `"2"`.
+This is also not a bug, though if you need different behavior, there is a solution in the next section.
+
+### Extensions
+
+You can write your own arbitrary extensions:
+
+* hash load extensions operate on the hash, and nested hashes
+ * use `::load_hash_extensions.add(:extension_name) {}`
+* load extensions operate on the values, and nested hash values, if any
+ * use `::load_extensions.add(:extension_name) {}`
+* dump extensions operate on the values, and nested hash values, if any
+ * use `::dump_extensions.add(:extension_name) {}`
+
+#### Example
+
+Let's say I want all integer-like keys, except 0, to be integer keys,
+while 0 converts to, and stays, a string forever.
+
+```ruby
+class MyExtSnakedHash < Hashie::Mash
+ include SnakyHash::Snake.new(
+ key_type: :symbol, # default :string
+ serializer: true, # default: false
+ )
+end
+
+MyExtSnakedHash.load_hash_extensions.add(:non_zero_keys_to_int) do |value|
+ if value.is_a?(Hash)
+ value.transform_keys do |key|
+ key_int = key.to_s.to_i
+ if key_int > 0
+ key_int
+ else
+ key
+ end
+ end
+ else
+ value
+ end
+end
+
+snake = MyExtSnakedHash.new(1 => "a", 0 => 4, "VeryFineHat" => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}) # => {1 => "a", 0 => 4, very_fine_hat: {3 => "v", 5 => 7, very_fine_hat: "feathers"}}
+dump = MyExtSnakedHash.dump(snake) # => "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
+hydrated = MyExtSnakedHash.load(dump) # => {1 => "a", "0": 4, very_fine_hat: {3 => "v", 5 => 7, very_fine_hat: "feathers"}}
+hydrated.class # => MyExtSnakedHash
+hydrated["1"] # => nil
+hydrated[1] # => "a"
+hydrated["2"] # => nil
+hydrated[2] # => 4
+hydrated["0"] # => 4
+hydrated[0] # => nil
+hydrated.very_fine_hat # => {3 => "v", 5 => 7, very_fine_hat: "feathers"}
+hydrated.very_fine_hat.very_fine_hat # => "feathers"
+hydrated.very_fine_hat[:very_fine_hat] # => 'Feathers'
+hydrated.very_fine_hat["very_fine_hat"] # => 'Feathers'
+```
+
### Stranger Things
I don't recommend using these features... but they exist (for now).
You can still access the original un-snaked camel keys.
And through them you can even use un-snaked camel methods.
+But don't.
```ruby
snake.key?("VeryFineHat") # => true
diff --git a/doc/SnakyHash.html b/doc/SnakyHash.html
index f21b4cb..3659455 100644
--- a/doc/SnakyHash.html
+++ b/doc/SnakyHash.html
@@ -123,7 +123,7 @@ Overview
diff --git a/doc/SnakyHash/Error.html b/doc/SnakyHash/Error.html
index 189a40d..1c5fa1f 100644
--- a/doc/SnakyHash/Error.html
+++ b/doc/SnakyHash/Error.html
@@ -114,7 +114,7 @@
diff --git a/doc/SnakyHash/Extensions.html b/doc/SnakyHash/Extensions.html
index 226b2fe..e62d619 100644
--- a/doc/SnakyHash/Extensions.html
+++ b/doc/SnakyHash/Extensions.html
@@ -446,7 +446,7 @@
diff --git a/doc/SnakyHash/Serializer.html b/doc/SnakyHash/Serializer.html
index b1da019..042d1ba 100644
--- a/doc/SnakyHash/Serializer.html
+++ b/doc/SnakyHash/Serializer.html
@@ -303,7 +303,7 @@
def load(raw_hash)
hash = JSON.parse(presence(raw_hash) || "{}")
- hash = load_hash(hash)
+ hash = load_value(self[hash])
new(hash)
end
@@ -316,7 +316,7 @@
diff --git a/doc/SnakyHash/Serializer/BackportedInstanceMethods.html b/doc/SnakyHash/Serializer/BackportedInstanceMethods.html
index e487fc2..4b98b20 100644
--- a/doc/SnakyHash/Serializer/BackportedInstanceMethods.html
+++ b/doc/SnakyHash/Serializer/BackportedInstanceMethods.html
@@ -190,7 +190,7 @@
This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails.
-This gem is used by oauth, oauth2, and other, gems to normalize hash keys to snake_case and lookups,
+
This gem is used by oauth and oauth2 gems to normalize hash keys to snake_case and lookups,
and provide a nice psuedo-object interface.
It can be thought of as a mashup, with upgrades, to the Rash (specifically the rash_alt flavor), which is a special Mash, made popular by the hashie gem, and the serialized_hashie gem by krystal.
@@ -361,7 +361,8 @@ 🔧 Basic Usage
snake = MySnakedHash.new(:a => "a", "b" => "b", 2 => 2, "VeryFineHat" => "Feathers")
snake.a # => 'a'
snake.b # => 'b'
-snake[2] # 2
+snake[2] # => 2
+snake["2"] # => nil, note that this gem only affects string / symbol keys.
snake.very_fine_hat # => 'Feathers'
snake[:very_fine_hat] # => 'Feathers'
snake["very_fine_hat"] # => 'Feathers'
@@ -372,11 +373,109 @@ 🔧 Basic Usage
Note also that keys which do not respond to to_sym, because they don’t have a natural conversion to a Symbol,
are left as-is.
+Serialization
+
+class MySerializedSnakedHash < Hashie::Mash
+ include SnakyHash::Snake.new(
+ key_type: :symbol, # default :string
+ serializer: true, # default: false
+ )
+end
+
+snake = MySerializedSnakedHash.new(:a => "a", "b" => "b", 2 => 2, "VeryFineHat" => "Feathers") # => {a: "a", b: "b", 2 => 2, very_fine_hat: "Feathers"}
+dump = MySerializedSnakedHash.dump(snake) # => "{\"a\":\"a\",\"b\":\"b\",\"2\":2,\"very_fine_hat\":\"Feathers\"}"
+hydrated = MySerializedSnakedHash.load(dump) # => {a: "a", b: "b", "2": 2, very_fine_hat: "Feathers"}
+hydrated.class # => MySerializedSnakedHash
+hydrated.a # => 'a'
+hydrated.b # => 'b'
+hydrated[2] # => nil # NOTE: this is the opposite of snake[2] => 2
+hydrated["2"] # => 2 # NOTE: this is the opposite of snake["2"] => nil
+hydrated.very_fine_hat # => 'Feathers'
+hydrated[:very_fine_hat] # => 'Feathers'
+hydrated["very_fine_hat"] # => 'Feathers'
+
+
+Note that the key VeryFineHat changed to very_fine_hat.
+That is indeed the point of this library, so not a bug.
+
+Note that the key 2 changed to "2" (because JSON keys are strings).
+When the JSON dump was reloaded it did not know to restore it as 2 instead of "2".
+This is also not a bug, though if you need different behavior, there is a solution in the next section.
+
+Extensions
+
+You can write your own arbitrary extensions:
+
+
+ - hash load extensions operate on the hash, and nested hashes
+
+ - use
::load_hash_extensions.add(:extension_name) {}
+
+
+
+ - load extensions operate on the values, and nested hash values, if any
+
+ - use
::load_extensions.add(:extension_name) {}
+
+
+
+ - dump extensions operate on the values, and nested hash values, if any
+
+ - use
::dump_extensions.add(:extension_name) {}
+
+
+
+
+
+Example
+
+Let’s say I want all integer-like keys, except 0, to be integer keys,
+while 0 converts to, and stays, a string forever.
+
+class MyExtSnakedHash < Hashie::Mash
+ include SnakyHash::Snake.new(
+ key_type: :symbol, # default :string
+ serializer: true, # default: false
+ )
+end
+
+MyExtSnakedHash.load_hash_extensions.add(:non_zero_keys_to_int) do |value|
+ if value.is_a?(Hash)
+ value.transform_keys do |key|
+ key_int = key.to_s.to_i
+ if key_int > 0
+ key_int
+ else
+ key
+ end
+ end
+ else
+ value
+ end
+end
+
+snake = MyExtSnakedHash.new(1 => "a", 0 => 4, "VeryFineHat" => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}) # => {1 => "a", 0 => 4, very_fine_hat: {3 => "v", 5 => 7, very_fine_hat: "feathers"}}
+dump = MyExtSnakedHash.dump(snake) # => "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
+hydrated = MyExtSnakedHash.load(dump) # => {1 => "a", "0": 4, very_fine_hat: {3 => "v", 5 => 7, very_fine_hat: "feathers"}}
+hydrated.class # => MyExtSnakedHash
+hydrated["1"] # => nil
+hydrated[1] # => "a"
+hydrated["2"] # => nil
+hydrated[2] # => 4
+hydrated["0"] # => 4
+hydrated[0] # => nil
+hydrated.very_fine_hat # => {3 => "v", 5 => 7, very_fine_hat: "feathers"}
+hydrated.very_fine_hat.very_fine_hat # => "feathers"
+hydrated.very_fine_hat[:very_fine_hat] # => 'Feathers'
+hydrated.very_fine_hat["very_fine_hat"] # => 'Feathers'
+
+
Stranger Things
I don’t recommend using these features… but they exist (for now).
You can still access the original un-snaked camel keys.
-And through them you can even use un-snaked camel methods.
+And through them you can even use un-snaked camel methods.
+But don’t.
snake.key?("VeryFineHat") # => true
snake["VeryFineHat"] # => 'Feathers'
@@ -525,7 +624,7 @@ 🤑 One more thing
diff --git a/doc/file.SECURITY.html b/doc/file.SECURITY.html
index d43bea8..ce91217 100644
--- a/doc/file.SECURITY.html
+++ b/doc/file.SECURITY.html
@@ -104,7 +104,7 @@ Snaky Hash for Enterprise
diff --git a/doc/index.html b/doc/index.html
index 93f1274..2784043 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -67,7 +67,7 @@
This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails.
-This gem is used by oauth, oauth2, and other, gems to normalize hash keys to snake_case and lookups,
+
This gem is used by oauth and oauth2 gems to normalize hash keys to snake_case and lookups,
and provide a nice psuedo-object interface.
It can be thought of as a mashup, with upgrades, to the Rash (specifically the rash_alt flavor), which is a special Mash, made popular by the hashie gem, and the serialized_hashie gem by krystal.
@@ -361,7 +361,8 @@ 🔧 Basic Usage
snake = MySnakedHash.new(:a => "a", "b" => "b", 2 => 2, "VeryFineHat" => "Feathers")
snake.a # => 'a'
snake.b # => 'b'
-snake[2] # 2
+snake[2] # => 2
+snake["2"] # => nil, note that this gem only affects string / symbol keys.
snake.very_fine_hat # => 'Feathers'
snake[:very_fine_hat] # => 'Feathers'
snake["very_fine_hat"] # => 'Feathers'
@@ -372,11 +373,109 @@ 🔧 Basic Usage
Note also that keys which do not respond to to_sym, because they don’t have a natural conversion to a Symbol,
are left as-is.
+Serialization
+
+class MySerializedSnakedHash < Hashie::Mash
+ include SnakyHash::Snake.new(
+ key_type: :symbol, # default :string
+ serializer: true, # default: false
+ )
+end
+
+snake = MySerializedSnakedHash.new(:a => "a", "b" => "b", 2 => 2, "VeryFineHat" => "Feathers") # => {a: "a", b: "b", 2 => 2, very_fine_hat: "Feathers"}
+dump = MySerializedSnakedHash.dump(snake) # => "{\"a\":\"a\",\"b\":\"b\",\"2\":2,\"very_fine_hat\":\"Feathers\"}"
+hydrated = MySerializedSnakedHash.load(dump) # => {a: "a", b: "b", "2": 2, very_fine_hat: "Feathers"}
+hydrated.class # => MySerializedSnakedHash
+hydrated.a # => 'a'
+hydrated.b # => 'b'
+hydrated[2] # => nil # NOTE: this is the opposite of snake[2] => 2
+hydrated["2"] # => 2 # NOTE: this is the opposite of snake["2"] => nil
+hydrated.very_fine_hat # => 'Feathers'
+hydrated[:very_fine_hat] # => 'Feathers'
+hydrated["very_fine_hat"] # => 'Feathers'
+
+
+Note that the key VeryFineHat changed to very_fine_hat.
+That is indeed the point of this library, so not a bug.
+
+Note that the key 2 changed to "2" (because JSON keys are strings).
+When the JSON dump was reloaded it did not know to restore it as 2 instead of "2".
+This is also not a bug, though if you need different behavior, there is a solution in the next section.
+
+Extensions
+
+You can write your own arbitrary extensions:
+
+
+ - hash load extensions operate on the hash, and nested hashes
+
+ - use
::load_hash_extensions.add(:extension_name) {}
+
+
+
+ - load extensions operate on the values, and nested hash values, if any
+
+ - use
::load_extensions.add(:extension_name) {}
+
+
+
+ - dump extensions operate on the values, and nested hash values, if any
+
+ - use
::dump_extensions.add(:extension_name) {}
+
+
+
+
+
+Example
+
+Let’s say I want all integer-like keys, except 0, to be integer keys,
+while 0 converts to, and stays, a string forever.
+
+class MyExtSnakedHash < Hashie::Mash
+ include SnakyHash::Snake.new(
+ key_type: :symbol, # default :string
+ serializer: true, # default: false
+ )
+end
+
+MyExtSnakedHash.load_hash_extensions.add(:non_zero_keys_to_int) do |value|
+ if value.is_a?(Hash)
+ value.transform_keys do |key|
+ key_int = key.to_s.to_i
+ if key_int > 0
+ key_int
+ else
+ key
+ end
+ end
+ else
+ value
+ end
+end
+
+snake = MyExtSnakedHash.new(1 => "a", 0 => 4, "VeryFineHat" => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}) # => {1 => "a", 0 => 4, very_fine_hat: {3 => "v", 5 => 7, very_fine_hat: "feathers"}}
+dump = MyExtSnakedHash.dump(snake) # => "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
+hydrated = MyExtSnakedHash.load(dump) # => {1 => "a", "0": 4, very_fine_hat: {3 => "v", 5 => 7, very_fine_hat: "feathers"}}
+hydrated.class # => MyExtSnakedHash
+hydrated["1"] # => nil
+hydrated[1] # => "a"
+hydrated["2"] # => nil
+hydrated[2] # => 4
+hydrated["0"] # => 4
+hydrated[0] # => nil
+hydrated.very_fine_hat # => {3 => "v", 5 => 7, very_fine_hat: "feathers"}
+hydrated.very_fine_hat.very_fine_hat # => "feathers"
+hydrated.very_fine_hat[:very_fine_hat] # => 'Feathers'
+hydrated.very_fine_hat["very_fine_hat"] # => 'Feathers'
+
+
Stranger Things
I don’t recommend using these features… but they exist (for now).
You can still access the original un-snaked camel keys.
-And through them you can even use un-snaked camel methods.
+And through them you can even use un-snaked camel methods.
+But don’t.
snake.key?("VeryFineHat") # => true
snake["VeryFineHat"] # => 'Feathers'
@@ -525,7 +624,7 @@ 🤑 One more thing
diff --git a/doc/top-level-namespace.html b/doc/top-level-namespace.html
index e4d20a2..77581dc 100644
--- a/doc/top-level-namespace.html
+++ b/doc/top-level-namespace.html
@@ -100,7 +100,7 @@ Defined Under Namespace
diff --git a/lib/snaky_hash/serializer.rb b/lib/snaky_hash/serializer.rb
index dc900ff..94d7ac8 100644
--- a/lib/snaky_hash/serializer.rb
+++ b/lib/snaky_hash/serializer.rb
@@ -25,7 +25,7 @@ def dump(obj)
def load(raw_hash)
hash = JSON.parse(presence(raw_hash) || "{}")
- hash = load_hash(hash)
+ hash = load_value(self[hash])
new(hash)
end
@@ -107,7 +107,7 @@ def dump_value(value)
def load_hash(hash)
# The hash will be a raw hash, not a hash of this class.
# So first we make it a hash of this class.
- self[hash].transform_values do |value|
+ hash.transform_values do |value|
load_value(value)
end
end
diff --git a/spec/shared_examples/a_serialized_hash.rb b/spec/shared_examples/a_serialized_hash.rb
index 8641d9c..3ff313c 100644
--- a/spec/shared_examples/a_serialized_hash.rb
+++ b/spec/shared_examples/a_serialized_hash.rb
@@ -114,7 +114,7 @@
}
hash = subject.load('{"some_hash":{"name":"Michael"}}')
expect(hash).to be_a Hashie::Mash
- expect(hash).to eq({"some_hash" => {"namf" => "Michael"}})
+ expect(hash).to eq({"some_hasi" => {"namf" => "Michael"}})
end
it "passes hashes through their own extension and return non-hash values properly" do
From 227b8dbc22552c360ea659809b3a187c0eafa90b Mon Sep 17 00:00:00 2001
From: "Peter H. Boling"
Date: Thu, 22 May 2025 01:52:09 +0700
Subject: [PATCH 2/5] =?UTF-8?q?=E2=9C=A8=20Serialization=20Extensions=20(i?=
=?UTF-8?q?)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.rubocop_gradual.lock | 2 +-
README.md | 20 +++----
doc/SnakyHash.html | 2 +-
doc/SnakyHash/Error.html | 2 +-
doc/SnakyHash/Extensions.html | 2 +-
doc/SnakyHash/Serializer.html | 2 +-
.../Serializer/BackportedInstanceMethods.html | 2 +-
doc/SnakyHash/Serializer/Modulizer.html | 2 +-
doc/SnakyHash/Snake.html | 2 +-
doc/SnakyHash/Snake/SnakyModulizer.html | 2 +-
doc/SnakyHash/StringKeyed.html | 2 +-
doc/SnakyHash/SymbolKeyed.html | 2 +-
doc/SnakyHash/Version.html | 2 +-
doc/_index.html | 2 +-
doc/file.CHANGELOG.html | 2 +-
doc/file.CODE_OF_CONDUCT.html | 2 +-
doc/file.CONTRIBUTING.html | 2 +-
doc/file.LICENSE.html | 2 +-
doc/file.README.html | 22 ++++----
doc/file.SECURITY.html | 2 +-
doc/index.html | 22 ++++----
doc/top-level-namespace.html | 2 +-
spec/snaky_hash/snake_spec.rb | 55 +++++++++++++++++++
23 files changed, 106 insertions(+), 51 deletions(-)
diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock
index 3b21c76..be22f45 100644
--- a/.rubocop_gradual.lock
+++ b/.rubocop_gradual.lock
@@ -11,7 +11,7 @@
"spec/snaky_hash/bad_snake_spec.rb:3931746112": [
[3, 16, 11, "RSpec/DescribeClass: The first argument to describe should be the class or module being tested.", 1577626599]
],
- "spec/snaky_hash/snake_spec.rb:3264128361": [
+ "spec/snaky_hash/snake_spec.rb:3662358368": [
[4, 3, 92, "RSpec/LeakyConstantDeclaration: Stub class constant instead of declaring explicitly.", 3047242215]
]
}
diff --git a/README.md b/README.md
index 6d85a10..5cdace7 100644
--- a/README.md
+++ b/README.md
@@ -193,12 +193,12 @@ This is also not a bug, though if you need different behavior, there is a soluti
You can write your own arbitrary extensions:
-* hash load extensions operate on the hash, and nested hashes
- * use `::load_hash_extensions.add(:extension_name) {}`
-* load extensions operate on the values, and nested hash values, if any
- * use `::load_extensions.add(:extension_name) {}`
-* dump extensions operate on the values, and nested hash values, if any
- * use `::dump_extensions.add(:extension_name) {}`
+* "Hash Load" extensions operate on the hash, and nested hashes
+ * use `::load_hash_extensions.add(:extension_name) { |hash| }`
+* "Load" extensions operate on the values, and nested hash's values, if any
+ * use `::load_extensions.add(:extension_name) { |value| }`
+* "Dump" extensions operate on the values, and nested hash's values, if any
+ * use `::dump_extensions.add(:extension_name) { |value| }`
#### Example
@@ -234,16 +234,16 @@ hydrated = MyExtSnakedHash.load(dump) # => {1 => "a", "0": 4, very_fine_hat: {3
hydrated.class # => MyExtSnakedHash
hydrated["1"] # => nil
hydrated[1] # => "a"
-hydrated["2"] # => nil
-hydrated[2] # => 4
hydrated["0"] # => 4
hydrated[0] # => nil
hydrated.very_fine_hat # => {3 => "v", 5 => 7, very_fine_hat: "feathers"}
hydrated.very_fine_hat.very_fine_hat # => "feathers"
-hydrated.very_fine_hat[:very_fine_hat] # => 'Feathers'
-hydrated.very_fine_hat["very_fine_hat"] # => 'Feathers'
+hydrated.very_fine_hat[:very_fine_hat] # => 'feathers'
+hydrated.very_fine_hat["very_fine_hat"] # => 'feathers'
```
+See the specs for more examples.
+
### Stranger Things
I don't recommend using these features... but they exist (for now).
diff --git a/doc/SnakyHash.html b/doc/SnakyHash.html
index 3659455..a75e807 100644
--- a/doc/SnakyHash.html
+++ b/doc/SnakyHash.html
@@ -123,7 +123,7 @@ Overview
diff --git a/doc/SnakyHash/Error.html b/doc/SnakyHash/Error.html
index 1c5fa1f..b052507 100644
--- a/doc/SnakyHash/Error.html
+++ b/doc/SnakyHash/Error.html
@@ -114,7 +114,7 @@
diff --git a/doc/SnakyHash/Extensions.html b/doc/SnakyHash/Extensions.html
index e62d619..fe919be 100644
--- a/doc/SnakyHash/Extensions.html
+++ b/doc/SnakyHash/Extensions.html
@@ -446,7 +446,7 @@
diff --git a/doc/SnakyHash/Serializer.html b/doc/SnakyHash/Serializer.html
index 042d1ba..f9ce6e3 100644
--- a/doc/SnakyHash/Serializer.html
+++ b/doc/SnakyHash/Serializer.html
@@ -316,7 +316,7 @@
diff --git a/doc/SnakyHash/Serializer/BackportedInstanceMethods.html b/doc/SnakyHash/Serializer/BackportedInstanceMethods.html
index 4b98b20..29d4176 100644
--- a/doc/SnakyHash/Serializer/BackportedInstanceMethods.html
+++ b/doc/SnakyHash/Serializer/BackportedInstanceMethods.html
@@ -190,7 +190,7 @@
diff --git a/doc/SnakyHash/Serializer/Modulizer.html b/doc/SnakyHash/Serializer/Modulizer.html
index 0ea38fd..0ee3e36 100644
--- a/doc/SnakyHash/Serializer/Modulizer.html
+++ b/doc/SnakyHash/Serializer/Modulizer.html
@@ -189,7 +189,7 @@
diff --git a/doc/SnakyHash/Snake.html b/doc/SnakyHash/Snake.html
index 9ed66fe..b38e6eb 100644
--- a/doc/SnakyHash/Snake.html
+++ b/doc/SnakyHash/Snake.html
@@ -275,7 +275,7 @@
diff --git a/doc/SnakyHash/Snake/SnakyModulizer.html b/doc/SnakyHash/Snake/SnakyModulizer.html
index d8ca641..6aed04e 100644
--- a/doc/SnakyHash/Snake/SnakyModulizer.html
+++ b/doc/SnakyHash/Snake/SnakyModulizer.html
@@ -261,7 +261,7 @@
diff --git a/doc/SnakyHash/StringKeyed.html b/doc/SnakyHash/StringKeyed.html
index b9d1eaf..bf752ad 100644
--- a/doc/SnakyHash/StringKeyed.html
+++ b/doc/SnakyHash/StringKeyed.html
@@ -130,7 +130,7 @@ Overview
diff --git a/doc/SnakyHash/SymbolKeyed.html b/doc/SnakyHash/SymbolKeyed.html
index 0be8558..6856654 100644
--- a/doc/SnakyHash/SymbolKeyed.html
+++ b/doc/SnakyHash/SymbolKeyed.html
@@ -130,7 +130,7 @@ Overview
diff --git a/doc/SnakyHash/Version.html b/doc/SnakyHash/Version.html
index 278919d..f8a64a5 100644
--- a/doc/SnakyHash/Version.html
+++ b/doc/SnakyHash/Version.html
@@ -111,7 +111,7 @@
diff --git a/doc/_index.html b/doc/_index.html
index 48f86d5..4347740 100644
--- a/doc/_index.html
+++ b/doc/_index.html
@@ -214,7 +214,7 @@ Namespace Listing A-Z
diff --git a/doc/file.CHANGELOG.html b/doc/file.CHANGELOG.html
index a77efc6..35cbbc6 100644
--- a/doc/file.CHANGELOG.html
+++ b/doc/file.CHANGELOG.html
@@ -137,7 +137,7 @@ Added
diff --git a/doc/file.CODE_OF_CONDUCT.html b/doc/file.CODE_OF_CONDUCT.html
index 5f71472..c0a3181 100644
--- a/doc/file.CODE_OF_CONDUCT.html
+++ b/doc/file.CODE_OF_CONDUCT.html
@@ -192,7 +192,7 @@ Attribution
diff --git a/doc/file.CONTRIBUTING.html b/doc/file.CONTRIBUTING.html
index 44e09ec..3eab99a 100644
--- a/doc/file.CONTRIBUTING.html
+++ b/doc/file.CONTRIBUTING.html
@@ -195,7 +195,7 @@ To release a new version:
diff --git a/doc/file.LICENSE.html b/doc/file.LICENSE.html
index 2462ab8..c5b4c63 100644
--- a/doc/file.LICENSE.html
+++ b/doc/file.LICENSE.html
@@ -60,7 +60,7 @@
The MIT License (MIT)
Copyright (c) 2022, 2025 Peter Boling
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
diff --git a/doc/file.README.html b/doc/file.README.html
index b8addce..2448ef3 100644
--- a/doc/file.README.html
+++ b/doc/file.README.html
@@ -407,21 +407,21 @@ Extensions
You can write your own arbitrary extensions:
- - hash load extensions operate on the hash, and nested hashes
+
- “Hash Load” extensions operate on the hash, and nested hashes
- - use
::load_hash_extensions.add(:extension_name) {}
+ - use
::load_hash_extensions.add(:extension_name) { |hash| }
- - load extensions operate on the values, and nested hash values, if any
+
- “Load” extensions operate on the values, and nested hash’s values, if any
- - use
::load_extensions.add(:extension_name) {}
+ - use
::load_extensions.add(:extension_name) { |value| }
- - dump extensions operate on the values, and nested hash values, if any
+
- “Dump” extensions operate on the values, and nested hash’s values, if any
- - use
::dump_extensions.add(:extension_name) {}
+ - use
::dump_extensions.add(:extension_name) { |value| }
@@ -460,16 +460,16 @@ Example
hydrated.class # => MyExtSnakedHash
hydrated["1"] # => nil
hydrated[1] # => "a"
-hydrated["2"] # => nil
-hydrated[2] # => 4
hydrated["0"] # => 4
hydrated[0] # => nil
hydrated.very_fine_hat # => {3 => "v", 5 => 7, very_fine_hat: "feathers"}
hydrated.very_fine_hat.very_fine_hat # => "feathers"
-hydrated.very_fine_hat[:very_fine_hat] # => 'Feathers'
-hydrated.very_fine_hat["very_fine_hat"] # => 'Feathers'
+hydrated.very_fine_hat[:very_fine_hat] # => 'feathers'
+hydrated.very_fine_hat["very_fine_hat"] # => 'feathers'
+See the specs for more examples.
+
Stranger Things
I don’t recommend using these features… but they exist (for now).
@@ -624,7 +624,7 @@
🤑 One more thing
diff --git a/doc/file.SECURITY.html b/doc/file.SECURITY.html
index ce91217..68dcf12 100644
--- a/doc/file.SECURITY.html
+++ b/doc/file.SECURITY.html
@@ -104,7 +104,7 @@ Snaky Hash for Enterprise
diff --git a/doc/index.html b/doc/index.html
index 2784043..0a9f6d7 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -407,21 +407,21 @@ Extensions
You can write your own arbitrary extensions:
- - hash load extensions operate on the hash, and nested hashes
+
- “Hash Load” extensions operate on the hash, and nested hashes
- - use
::load_hash_extensions.add(:extension_name) {}
+ - use
::load_hash_extensions.add(:extension_name) { |hash| }
- - load extensions operate on the values, and nested hash values, if any
+
- “Load” extensions operate on the values, and nested hash’s values, if any
- - use
::load_extensions.add(:extension_name) {}
+ - use
::load_extensions.add(:extension_name) { |value| }
- - dump extensions operate on the values, and nested hash values, if any
+
- “Dump” extensions operate on the values, and nested hash’s values, if any
- - use
::dump_extensions.add(:extension_name) {}
+ - use
::dump_extensions.add(:extension_name) { |value| }
@@ -460,16 +460,16 @@ Example
hydrated.class # => MyExtSnakedHash
hydrated["1"] # => nil
hydrated[1] # => "a"
-hydrated["2"] # => nil
-hydrated[2] # => 4
hydrated["0"] # => 4
hydrated[0] # => nil
hydrated.very_fine_hat # => {3 => "v", 5 => 7, very_fine_hat: "feathers"}
hydrated.very_fine_hat.very_fine_hat # => "feathers"
-hydrated.very_fine_hat[:very_fine_hat] # => 'Feathers'
-hydrated.very_fine_hat["very_fine_hat"] # => 'Feathers'
+hydrated.very_fine_hat[:very_fine_hat] # => 'feathers'
+hydrated.very_fine_hat["very_fine_hat"] # => 'feathers'
+See the specs for more examples.
+
Stranger Things
I don’t recommend using these features… but they exist (for now).
@@ -624,7 +624,7 @@
🤑 One more thing
diff --git a/doc/top-level-namespace.html b/doc/top-level-namespace.html
index 77581dc..efa356b 100644
--- a/doc/top-level-namespace.html
+++ b/doc/top-level-namespace.html
@@ -100,7 +100,7 @@ Defined Under Namespace
diff --git a/spec/snaky_hash/snake_spec.rb b/spec/snaky_hash/snake_spec.rb
index 1add986..01117dc 100644
--- a/spec/snaky_hash/snake_spec.rb
+++ b/spec/snaky_hash/snake_spec.rb
@@ -32,4 +32,59 @@ class TheSnakedHash < Hashie::Mash
expect(res).not_to be_a(TheSnakedHash)
expect(res).to be_a(Hash)
end
+
+ context "when serializer: true" do
+ let(:snaky_klass) do
+ klass = Class.new(Hashie::Mash) do
+ include SnakyHash::Snake.new(
+ key_type: :symbol, # default :string
+ serializer: true, # default: false
+ )
+ end
+
+ klass.load_hash_extensions.add(:non_zero_keys_to_int) do |value|
+ if value.is_a?(Hash)
+ value.transform_keys do |key|
+ key_int = key.to_s.to_i
+ if key_int > 0
+ key_int
+ else
+ key
+ end
+ end
+ else
+ value
+ end
+ end
+
+ klass
+ end
+
+ let(:snake) { snaky_klass.new(1 => "a", 0 => 4, "VeryFineHat" => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}) }
+ let(:dump) { snaky_klass.dump(snake) }
+ let(:hydrated) { snaky_klass.load(dump) }
+
+ it "can initialize" do
+ expect(snake).to eq({1 => "a", 0 => 4, :very_fine_hat => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}})
+ end
+
+ it "can dump" do
+ expect(dump).to eq "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
+ end
+
+ it "can load" do
+ expect(hydrated).to eq({1 => "a", :"0" => 4, :very_fine_hat => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}})
+ end
+
+ it "can access keys" do
+ expect(hydrated["1"]).to be_nil
+ expect(hydrated[1]).to eq("a")
+ expect(hydrated["0"]).to eq(4)
+ expect(hydrated[0]).to be_nil
+ expect(hydrated.very_fine_hat).to eq({3 => "v", 5 => 7, :very_fine_hat => "feathers"})
+ expect(hydrated.very_fine_hat.very_fine_hat).to eq("feathers")
+ expect(hydrated.very_fine_hat[:very_fine_hat]).to eq("feathers")
+ expect(hydrated.very_fine_hat["very_fine_hat"]).to eq("feathers")
+ end
+ end
end
From b9f9353d6eac2da5b3ba3e055c69d34f1bd5696f Mon Sep 17 00:00:00 2001
From: "Peter H. Boling"
Date: Thu, 22 May 2025 02:02:18 +0700
Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=93=9D=20Documentation=20updates?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CHANGELOG.md | 16 ++++++++++++----
README.md | 15 ++++++++++-----
2 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d9e5d1..25082fd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,15 +6,21 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2.
## [Unreleased]
### Added
+### Changed
+### Fixed
+### Removed
+
+## [2.0.2] - 2025-05-21 ([tag][2.0.2t])
+### Added
- Gem is signed by 20-year cert (@pboling)
- Expires 2045-04-29
- Gemspec metadata updates (@pboling)
- Documentation updates (@pboling)
- CI covering all code, and all key versions of runtime dependencies (@pboling)
+ - Including Hashie v0, v1, v2, v3, v4, v5, and HEAD
- [gh2](https://github.com/oauth-xx/snaky_hash/pull/2) - Serializer option (@pboling)
-### Changed
-### Fixed
-### Removed
+- [gh3](https://github.com/oauth-xx/snaky_hash/pull/3) - Serializer Extensions (@pboling)
+- Documentation site at [snaky-hash.galtzo.com](https://snaky-hash.galtzo.com)
## [2.0.1] - 2022-09-23 ([tag][2.0.1t])
### Added
@@ -48,7 +54,9 @@ end
### Added
- Initial release
-[Unreleased]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.1...main
+[Unreleased]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.2...main
+[2.0.21]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.1...v2.0.2
+[2.0.2t]: https://gitlab.com/oauth-xx/snaky_hash/-/releases/tag/v2.0.2
[2.0.1]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.0...v2.0.1
[2.0.1t]: https://gitlab.com/oauth-xx/snaky_hash/-/releases/tag/v2.0.1
[2.0.0]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v1.0.1...v2.0.0
diff --git a/README.md b/README.md
index 5cdace7..28f1241 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# SnakyHash
+# 🐍 SnakyHash
[![Version][👽versioni]][👽version] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![Depfu][🔑depfui♻️]][🔑depfu] [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls] [![QLTY Test Coverage][🔑qlty-covi♻️]][🔑qlty-cov] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL]
@@ -6,14 +6,17 @@
[![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
-This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails.
+This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails, but does a lot more.
This gem is used by `oauth` and `oauth2` gems to normalize hash keys to `snake_case` and lookups,
and provide a nice psuedo-object interface.
-It can be thought of as a mashup, with upgrades, to the `Rash` (specifically the [`rash_alt`](https://github.com/shishi/rash_alt) flavor), which is a special `Mash`, made popular by the `hashie` gem, and the `serialized_hashie` [gem by krystal](https://github.com/krystal/serialized-hashie).
+It can be thought of as a mashup of:
-Classes that include `SnakyHash::Snake` should inherit from `Hashie::Mash`.
+* `Rash` (specifically the [`rash_alt`](https://github.com/shishi/rash_alt) flavor), which is a special `Mash`, made popular by the `hashie` gem, and
+* `serialized_hashie` [gem by krystal](https://github.com/krystal/serialized-hashie)
+
+Classes that `include SnakyHash::Snake.new` should inherit from `Hashie::Mash`.
## New for v2.0.2: Serialization Support
@@ -29,6 +32,8 @@ class MyStringKeyedHash < Hashie::Mash
end
```
+✨ Also new dump & load plugin extensions to control the way your data is dumped and loaded.
+
| Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
|-----------------------------------------------|-------------------------------------------------------------------|---------------------------|--------------------------|---------------------------|--------------------------|------------------------------|
| 🧪 [oauth-xx/snaky_hash on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜wiki] | 🏀 Tiny Matrix | ➖ |
@@ -187,7 +192,7 @@ That is indeed the point of this library, so not a bug.
Note that the key `2` changed to `"2"` (because JSON keys are strings).
When the JSON dump was reloaded it did not know to restore it as `2` instead of `"2"`.
-This is also not a bug, though if you need different behavior, there is a solution in the next section.
+This is also not a bug, though if you need different behavior, there is a solution in the [next section](#extensions).
### Extensions
From 832a2406b474a8cd4b3a741a29f0010049b01b00 Mon Sep 17 00:00:00 2001
From: "Peter H. Boling"
Date: Thu, 22 May 2025 02:24:48 +0700
Subject: [PATCH 4/5] =?UTF-8?q?=E2=9C=A8=20Serialization=20Extensions=20(i?=
=?UTF-8?q?i)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.rubocop_gradual.lock | 2 +-
doc/SnakyHash.html | 2 +-
doc/SnakyHash/Error.html | 2 +-
doc/SnakyHash/Extensions.html | 2 +-
doc/SnakyHash/Serializer.html | 4 +-
.../Serializer/BackportedInstanceMethods.html | 2 +-
doc/SnakyHash/Serializer/Modulizer.html | 2 +-
doc/SnakyHash/Snake.html | 2 +-
doc/SnakyHash/Snake/SnakyModulizer.html | 2 +-
doc/SnakyHash/StringKeyed.html | 2 +-
doc/SnakyHash/SymbolKeyed.html | 2 +-
doc/SnakyHash/Version.html | 2 +-
doc/_index.html | 2 +-
doc/file.CHANGELOG.html | 34 +++--
doc/file.CODE_OF_CONDUCT.html | 2 +-
doc/file.CONTRIBUTING.html | 2 +-
doc/file.LICENSE.html | 2 +-
doc/file.README.html | 22 ++-
doc/file.SECURITY.html | 2 +-
doc/index.html | 22 ++-
doc/top-level-namespace.html | 2 +-
lib/snaky_hash/serializer.rb | 7 +-
spec/snaky_hash/snake_spec.rb | 138 +++++++++++++-----
23 files changed, 176 insertions(+), 85 deletions(-)
diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock
index be22f45..a452dee 100644
--- a/.rubocop_gradual.lock
+++ b/.rubocop_gradual.lock
@@ -11,7 +11,7 @@
"spec/snaky_hash/bad_snake_spec.rb:3931746112": [
[3, 16, 11, "RSpec/DescribeClass: The first argument to describe should be the class or module being tested.", 1577626599]
],
- "spec/snaky_hash/snake_spec.rb:3662358368": [
+ "spec/snaky_hash/snake_spec.rb:2603178792": [
[4, 3, 92, "RSpec/LeakyConstantDeclaration: Stub class constant instead of declaring explicitly.", 3047242215]
]
}
diff --git a/doc/SnakyHash.html b/doc/SnakyHash.html
index a75e807..f9e5ba3 100644
--- a/doc/SnakyHash.html
+++ b/doc/SnakyHash.html
@@ -123,7 +123,7 @@ Overview
diff --git a/doc/SnakyHash/Error.html b/doc/SnakyHash/Error.html
index b052507..b6b0888 100644
--- a/doc/SnakyHash/Error.html
+++ b/doc/SnakyHash/Error.html
@@ -114,7 +114,7 @@
diff --git a/doc/SnakyHash/Extensions.html b/doc/SnakyHash/Extensions.html
index fe919be..00fda6b 100644
--- a/doc/SnakyHash/Extensions.html
+++ b/doc/SnakyHash/Extensions.html
@@ -446,7 +446,7 @@
diff --git a/doc/SnakyHash/Serializer.html b/doc/SnakyHash/Serializer.html
index f9ce6e3..aea3bc4 100644
--- a/doc/SnakyHash/Serializer.html
+++ b/doc/SnakyHash/Serializer.html
@@ -303,7 +303,7 @@
def load(raw_hash)
hash = JSON.parse(presence(raw_hash) || "{}")
- hash = load_value(self[hash])
+ hash = load_value(new(hash))
new(hash)
end
@@ -316,7 +316,7 @@
diff --git a/doc/SnakyHash/Serializer/BackportedInstanceMethods.html b/doc/SnakyHash/Serializer/BackportedInstanceMethods.html
index 29d4176..933efe1 100644
--- a/doc/SnakyHash/Serializer/BackportedInstanceMethods.html
+++ b/doc/SnakyHash/Serializer/BackportedInstanceMethods.html
@@ -190,7 +190,7 @@
diff --git a/doc/SnakyHash/Serializer/Modulizer.html b/doc/SnakyHash/Serializer/Modulizer.html
index 0ee3e36..3763e67 100644
--- a/doc/SnakyHash/Serializer/Modulizer.html
+++ b/doc/SnakyHash/Serializer/Modulizer.html
@@ -189,7 +189,7 @@
diff --git a/doc/SnakyHash/Snake.html b/doc/SnakyHash/Snake.html
index b38e6eb..e515952 100644
--- a/doc/SnakyHash/Snake.html
+++ b/doc/SnakyHash/Snake.html
@@ -275,7 +275,7 @@
diff --git a/doc/SnakyHash/Snake/SnakyModulizer.html b/doc/SnakyHash/Snake/SnakyModulizer.html
index 6aed04e..e1460e8 100644
--- a/doc/SnakyHash/Snake/SnakyModulizer.html
+++ b/doc/SnakyHash/Snake/SnakyModulizer.html
@@ -261,7 +261,7 @@
diff --git a/doc/SnakyHash/StringKeyed.html b/doc/SnakyHash/StringKeyed.html
index bf752ad..e558438 100644
--- a/doc/SnakyHash/StringKeyed.html
+++ b/doc/SnakyHash/StringKeyed.html
@@ -130,7 +130,7 @@ Overview
diff --git a/doc/SnakyHash/SymbolKeyed.html b/doc/SnakyHash/SymbolKeyed.html
index 6856654..1b37ba7 100644
--- a/doc/SnakyHash/SymbolKeyed.html
+++ b/doc/SnakyHash/SymbolKeyed.html
@@ -130,7 +130,7 @@ Overview
diff --git a/doc/SnakyHash/Version.html b/doc/SnakyHash/Version.html
index f8a64a5..06f72ef 100644
--- a/doc/SnakyHash/Version.html
+++ b/doc/SnakyHash/Version.html
@@ -111,7 +111,7 @@
diff --git a/doc/_index.html b/doc/_index.html
index 4347740..d00c656 100644
--- a/doc/_index.html
+++ b/doc/_index.html
@@ -214,7 +214,7 @@ Namespace Listing A-Z
diff --git a/doc/file.CHANGELOG.html b/doc/file.CHANGELOG.html
index 35cbbc6..c381ba7 100644
--- a/doc/file.CHANGELOG.html
+++ b/doc/file.CHANGELOG.html
@@ -63,8 +63,14 @@
The format (since v2) is based on Keep a Changelog v1,
and this project adheres to Semantic Versioning v2.
-Unreleased
+Unreleased
Added
+Changed
+Fixed
+Removed
+
+[2.0.2] - 2025-05-21 (tag)
+Added
- Gem is signed by 20-year cert (@pboling)
@@ -73,18 +79,22 @@ Added
- Gemspec metadata updates (@pboling)
- Documentation updates (@pboling)
- - CI covering all code, and all key versions of runtime dependencies (@pboling)
- -
-gh2 - Serializer option (@pboling)
-
Changed
- Fixed
- Removed
+ - CI covering all code, and all key versions of runtime dependencies (@pboling)
+
+ - Including Hashie v0, v1, v2, v3, v4, v5, and HEAD
+
+ -
+gh2 - Serializer option (@pboling)
+ -
+gh3 - Serializer Extensions (@pboling)
+ - Documentation site at snaky-hash.galtzo.com
+
2.0.1 - 2022-09-23 (tag)
-Added
+Added
- Certificate for signing gem releases (@pboling)
- Gemspec metadata (@pboling)
@@ -109,7 +119,7 @@
Changed
include SnakyHash::Snake.new(key_type: :string) # or :symbol
end
- Added
+ Added
-
SnakyHash::StringKeyed: a Hashie::Mash class with snake-cased String keys
@@ -119,7 +129,7 @@ Added
1.0.1 - 2022-08-26 (tag)
-Added
+Added
- Missing LICENSE.txt file to release
Removed
@@ -129,7 +139,7 @@ Removed
1.0.0 - 2022-08-26 (tag)
-Added
+Added
@@ -137,7 +147,7 @@ Added
diff --git a/doc/file.CODE_OF_CONDUCT.html b/doc/file.CODE_OF_CONDUCT.html
index c0a3181..dbeb4e0 100644
--- a/doc/file.CODE_OF_CONDUCT.html
+++ b/doc/file.CODE_OF_CONDUCT.html
@@ -192,7 +192,7 @@ Attribution
diff --git a/doc/file.CONTRIBUTING.html b/doc/file.CONTRIBUTING.html
index 3eab99a..127b0bb 100644
--- a/doc/file.CONTRIBUTING.html
+++ b/doc/file.CONTRIBUTING.html
@@ -195,7 +195,7 @@ To release a new version:
diff --git a/doc/file.LICENSE.html b/doc/file.LICENSE.html
index c5b4c63..7440666 100644
--- a/doc/file.LICENSE.html
+++ b/doc/file.LICENSE.html
@@ -60,7 +60,7 @@
The MIT License (MIT)
Copyright (c) 2022, 2025 Peter Boling
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
diff --git a/doc/file.README.html b/doc/file.README.html
index 2448ef3..e374163 100644
--- a/doc/file.README.html
+++ b/doc/file.README.html
@@ -57,7 +57,7 @@
- SnakyHash
+
🐍 SnakyHash

@@ -65,14 +65,22 @@

-
This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails.
+
This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails, but does a lot more.
This gem is used by oauth and oauth2 gems to normalize hash keys to snake_case and lookups,
and provide a nice psuedo-object interface.
-
It can be thought of as a mashup, with upgrades, to the Rash (specifically the rash_alt flavor), which is a special Mash, made popular by the hashie gem, and the serialized_hashie gem by krystal.
+
It can be thought of as a mashup of:
-
Classes that include SnakyHash::Snake should inherit from Hashie::Mash.
+
+ -
+
Rash (specifically the rash_alt flavor), which is a special Mash, made popular by the hashie gem, and
+ -
+
serialized_hashie gem by krystal
+
+
+
+
Classes that include SnakyHash::Snake.new should inherit from Hashie::Mash.
New for v2.0.2: Serialization Support
@@ -87,6 +95,8 @@
New for v2.0.2: Serialization Suppor
end
+
✨ Also new dump & load plugin extensions to control the way your data is dumped and loaded.
+
@@ -400,7 +410,7 @@ Serialization
Note that the key 2 changed to "2" (because JSON keys are strings).
When the JSON dump was reloaded it did not know to restore it as 2 instead of "2".
-This is also not a bug, though if you need different behavior, there is a solution in the next section.
+This is also not a bug, though if you need different behavior, there is a solution in the next section.
Extensions
@@ -624,7 +634,7 @@ 🤑 One more thing
diff --git a/doc/file.SECURITY.html b/doc/file.SECURITY.html
index 68dcf12..d2bfdcf 100644
--- a/doc/file.SECURITY.html
+++ b/doc/file.SECURITY.html
@@ -104,7 +104,7 @@ Snaky Hash for Enterprise
diff --git a/doc/index.html b/doc/index.html
index 0a9f6d7..ca8eb47 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -57,7 +57,7 @@
- SnakyHash
+
🐍 SnakyHash

@@ -65,14 +65,22 @@

-
This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails.
+
This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails, but does a lot more.
This gem is used by oauth and oauth2 gems to normalize hash keys to snake_case and lookups,
and provide a nice psuedo-object interface.
-
It can be thought of as a mashup, with upgrades, to the Rash (specifically the rash_alt flavor), which is a special Mash, made popular by the hashie gem, and the serialized_hashie gem by krystal.
+
It can be thought of as a mashup of:
-
Classes that include SnakyHash::Snake should inherit from Hashie::Mash.
+
+ -
+
Rash (specifically the rash_alt flavor), which is a special Mash, made popular by the hashie gem, and
+ -
+
serialized_hashie gem by krystal
+
+
+
+
Classes that include SnakyHash::Snake.new should inherit from Hashie::Mash.
New for v2.0.2: Serialization Support
@@ -87,6 +95,8 @@
New for v2.0.2: Serialization Suppor
end
+
✨ Also new dump & load plugin extensions to control the way your data is dumped and loaded.
+
@@ -400,7 +410,7 @@ Serialization
Note that the key 2 changed to "2" (because JSON keys are strings).
When the JSON dump was reloaded it did not know to restore it as 2 instead of "2".
-This is also not a bug, though if you need different behavior, there is a solution in the next section.
+This is also not a bug, though if you need different behavior, there is a solution in the next section.
Extensions
@@ -624,7 +634,7 @@ 🤑 One more thing
diff --git a/doc/top-level-namespace.html b/doc/top-level-namespace.html
index efa356b..f2e3075 100644
--- a/doc/top-level-namespace.html
+++ b/doc/top-level-namespace.html
@@ -100,7 +100,7 @@ Defined Under Namespace
diff --git a/lib/snaky_hash/serializer.rb b/lib/snaky_hash/serializer.rb
index 94d7ac8..e2c2124 100644
--- a/lib/snaky_hash/serializer.rb
+++ b/lib/snaky_hash/serializer.rb
@@ -25,7 +25,7 @@ def dump(obj)
def load(raw_hash)
hash = JSON.parse(presence(raw_hash) || "{}")
- hash = load_value(self[hash])
+ hash = load_value(new(hash))
new(hash)
end
@@ -114,10 +114,11 @@ def load_hash(hash)
def load_value(value)
if value.is_a?(::Hash)
- hash = load_hash_extensions.run(value)
+ # The extension might call `transform_keys, or similar, thus returning a new vanilla hash
+ hash = load_hash_extensions.run(new(value))
# If the result is still a hash, we'll return that here
- return load_hash(hash) if hash.is_a?(::Hash)
+ return load_hash(new(hash)) if hash.is_a?(::Hash)
# If the result is not a hash, we'll just return whatever
# was returned as a normal value.
diff --git a/spec/snaky_hash/snake_spec.rb b/spec/snaky_hash/snake_spec.rb
index 01117dc..e49540f 100644
--- a/spec/snaky_hash/snake_spec.rb
+++ b/spec/snaky_hash/snake_spec.rb
@@ -34,57 +34,117 @@ class TheSnakedHash < Hashie::Mash
end
context "when serializer: true" do
- let(:snaky_klass) do
- klass = Class.new(Hashie::Mash) do
- include SnakyHash::Snake.new(
- key_type: :symbol, # default :string
- serializer: true, # default: false
- )
- end
+ # Older versions of Ruby can't do Integer#to_sym,
+ # which means snaky_hash on older rubies can't handle integer keys via new
+ if RUBY_VERSION >= "3.0"
+ context "when Ruby >= 3.0" do
+ let(:snaky_klass) do
+ klass = Class.new(Hashie::Mash) do
+ include SnakyHash::Snake.new(
+ key_type: :symbol, # default :string
+ serializer: true, # default: false
+ )
+ end
- klass.load_hash_extensions.add(:non_zero_keys_to_int) do |value|
- if value.is_a?(Hash)
- value.transform_keys do |key|
- key_int = key.to_s.to_i
- if key_int > 0
- key_int
+ klass.load_hash_extensions.add(:non_zero_keys_to_int) do |value|
+ if value.is_a?(Hash)
+ value.transform_keys do |key|
+ key_int = key.to_s.to_i
+ if key_int > 0
+ key_int
+ else
+ key
+ end
+ end
else
- key
+ value
end
end
- else
- value
+
+ klass
+ end
+
+ let(:snake) { snaky_klass.new(1 => "a", 0 => 4, "VeryFineHat" => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}) }
+ let(:dump) { snaky_klass.dump(snake) }
+ let(:hydrated) { snaky_klass.load(dump) }
+
+ it "can initialize" do
+ expect(snake).to eq({1 => "a", 0 => 4, :very_fine_hat => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}})
+ end
+
+ it "can dump" do
+ expect(dump).to eq "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
+ end
+
+ it "can load" do
+ expect(hydrated).to eq({1 => "a", :"0" => 4, :very_fine_hat => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}})
+ end
+
+ it "can access keys" do
+ expect(hydrated["1"]).to be_nil
+ expect(hydrated[1]).to eq("a")
+ expect(hydrated["0"]).to eq(4)
+ expect(hydrated[0]).to be_nil
+ expect(hydrated.very_fine_hat).to eq({3 => "v", 5 => 7, :very_fine_hat => "feathers"})
+ expect(hydrated.very_fine_hat.very_fine_hat).to eq("feathers")
+ expect(hydrated.very_fine_hat[:very_fine_hat]).to eq("feathers")
+ expect(hydrated.very_fine_hat["very_fine_hat"]).to eq("feathers")
end
end
+ else
+ context "when Ruby < 3.0" do
+ let(:snaky_klass) do
+ klass = Class.new(Hashie::Mash) do
+ include SnakyHash::Snake.new(
+ key_type: :symbol, # default :string
+ serializer: true, # default: false
+ )
+ end
- klass
- end
+ klass.load_hash_extensions.add(:non_zero_keys_to_int) do |value|
+ if value.is_a?(Hash)
+ value.keys.each do |key|
+ key_int = key.to_s.to_i
+ if key_int > 0
+ ref = value.delete(key)
+ value[key_int] = ref
+ end
+ end
+ else
+ value
+ end
+ end
- let(:snake) { snaky_klass.new(1 => "a", 0 => 4, "VeryFineHat" => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}) }
- let(:dump) { snaky_klass.dump(snake) }
- let(:hydrated) { snaky_klass.load(dump) }
+ klass
+ end
- it "can initialize" do
- expect(snake).to eq({1 => "a", 0 => 4, :very_fine_hat => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}})
- end
+ let(:snake) { snaky_klass.new("1" => "a", "0" => 4, "VeryFineHat" => {"3" => "v", "5" => 7, :very_fine_hat => "feathers"}) }
+ let(:dump) { snaky_klass.dump(snake) }
+ let(:hydrated) { snaky_klass.load(dump) }
- it "can dump" do
- expect(dump).to eq "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
- end
+ it "can initialize" do
+ expect(snake).to eq({"1" => "a", "0" => 4, :very_fine_hat => {"3" => "v", "5" => 7, :very_fine_hat => "feathers"}})
+ end
- it "can load" do
- expect(hydrated).to eq({1 => "a", :"0" => 4, :very_fine_hat => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}})
- end
+ it "can dump" do
+ expect(dump).to eq "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
+ end
+
+ it "can load" do
+ expect(hydrated).to eq({1 => "a", :"0" => 4, :very_fine_hat => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}})
+ end
- it "can access keys" do
- expect(hydrated["1"]).to be_nil
- expect(hydrated[1]).to eq("a")
- expect(hydrated["0"]).to eq(4)
- expect(hydrated[0]).to be_nil
- expect(hydrated.very_fine_hat).to eq({3 => "v", 5 => 7, :very_fine_hat => "feathers"})
- expect(hydrated.very_fine_hat.very_fine_hat).to eq("feathers")
- expect(hydrated.very_fine_hat[:very_fine_hat]).to eq("feathers")
- expect(hydrated.very_fine_hat["very_fine_hat"]).to eq("feathers")
+ it "can access keys" do
+ expect(hydrated["1"]).to be_nil
+ expect(hydrated[1]).to eq("a")
+ expect(hydrated["0"]).to eq(4)
+ expect(hydrated[0]).to be_nil
+ expect(hydrated.very_fine_hat).to eq({3 => "v", 5 => 7, :very_fine_hat => "feathers"})
+ expect(hydrated.very_fine_hat.very_fine_hat).to eq("feathers")
+ expect(hydrated.very_fine_hat[:very_fine_hat]).to eq("feathers")
+ expect(hydrated.very_fine_hat["very_fine_hat"]).to eq("feathers")
+ end
+ end
end
end
end
From c63d03d172c83186318701535fce72795303994d Mon Sep 17 00:00:00 2001
From: "Peter H. Boling"
Date: Thu, 22 May 2025 03:35:20 +0700
Subject: [PATCH 5/5] =?UTF-8?q?=E2=9C=A8=20Serialization=20Extensions=20(i?=
=?UTF-8?q?ii)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.rubocop_gradual.lock | 2 +-
doc/SnakyHash.html | 2 +-
doc/SnakyHash/Error.html | 2 +-
doc/SnakyHash/Extensions.html | 2 +-
doc/SnakyHash/Serializer.html | 2 +-
.../Serializer/BackportedInstanceMethods.html | 2 +-
doc/SnakyHash/Serializer/Modulizer.html | 2 +-
doc/SnakyHash/Snake.html | 2 +-
doc/SnakyHash/Snake/SnakyModulizer.html | 2 +-
doc/SnakyHash/StringKeyed.html | 2 +-
doc/SnakyHash/SymbolKeyed.html | 2 +-
doc/SnakyHash/Version.html | 2 +-
doc/_index.html | 2 +-
doc/file.CHANGELOG.html | 2 +-
doc/file.CODE_OF_CONDUCT.html | 2 +-
doc/file.CONTRIBUTING.html | 2 +-
doc/file.LICENSE.html | 2 +-
doc/file.README.html | 2 +-
doc/file.SECURITY.html | 2 +-
doc/index.html | 2 +-
doc/top-level-namespace.html | 2 +-
spec/snaky_hash/snake_spec.rb | 159 +++++++-----------
22 files changed, 81 insertions(+), 120 deletions(-)
diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock
index a452dee..4ea5290 100644
--- a/.rubocop_gradual.lock
+++ b/.rubocop_gradual.lock
@@ -11,7 +11,7 @@
"spec/snaky_hash/bad_snake_spec.rb:3931746112": [
[3, 16, 11, "RSpec/DescribeClass: The first argument to describe should be the class or module being tested.", 1577626599]
],
- "spec/snaky_hash/snake_spec.rb:2603178792": [
+ "spec/snaky_hash/snake_spec.rb:1921604918": [
[4, 3, 92, "RSpec/LeakyConstantDeclaration: Stub class constant instead of declaring explicitly.", 3047242215]
]
}
diff --git a/doc/SnakyHash.html b/doc/SnakyHash.html
index f9e5ba3..ca07657 100644
--- a/doc/SnakyHash.html
+++ b/doc/SnakyHash.html
@@ -123,7 +123,7 @@ Overview
diff --git a/doc/SnakyHash/Error.html b/doc/SnakyHash/Error.html
index b6b0888..076414f 100644
--- a/doc/SnakyHash/Error.html
+++ b/doc/SnakyHash/Error.html
@@ -114,7 +114,7 @@
diff --git a/doc/SnakyHash/Extensions.html b/doc/SnakyHash/Extensions.html
index 00fda6b..6139613 100644
--- a/doc/SnakyHash/Extensions.html
+++ b/doc/SnakyHash/Extensions.html
@@ -446,7 +446,7 @@
diff --git a/doc/SnakyHash/Serializer.html b/doc/SnakyHash/Serializer.html
index aea3bc4..1366c77 100644
--- a/doc/SnakyHash/Serializer.html
+++ b/doc/SnakyHash/Serializer.html
@@ -316,7 +316,7 @@
diff --git a/doc/SnakyHash/Serializer/BackportedInstanceMethods.html b/doc/SnakyHash/Serializer/BackportedInstanceMethods.html
index 933efe1..6619ac2 100644
--- a/doc/SnakyHash/Serializer/BackportedInstanceMethods.html
+++ b/doc/SnakyHash/Serializer/BackportedInstanceMethods.html
@@ -190,7 +190,7 @@
diff --git a/doc/SnakyHash/Serializer/Modulizer.html b/doc/SnakyHash/Serializer/Modulizer.html
index 3763e67..d45638f 100644
--- a/doc/SnakyHash/Serializer/Modulizer.html
+++ b/doc/SnakyHash/Serializer/Modulizer.html
@@ -189,7 +189,7 @@
diff --git a/doc/SnakyHash/Snake.html b/doc/SnakyHash/Snake.html
index e515952..7217710 100644
--- a/doc/SnakyHash/Snake.html
+++ b/doc/SnakyHash/Snake.html
@@ -275,7 +275,7 @@
diff --git a/doc/SnakyHash/Snake/SnakyModulizer.html b/doc/SnakyHash/Snake/SnakyModulizer.html
index e1460e8..9a89620 100644
--- a/doc/SnakyHash/Snake/SnakyModulizer.html
+++ b/doc/SnakyHash/Snake/SnakyModulizer.html
@@ -261,7 +261,7 @@
diff --git a/doc/SnakyHash/StringKeyed.html b/doc/SnakyHash/StringKeyed.html
index e558438..06df63f 100644
--- a/doc/SnakyHash/StringKeyed.html
+++ b/doc/SnakyHash/StringKeyed.html
@@ -130,7 +130,7 @@ Overview
diff --git a/doc/SnakyHash/SymbolKeyed.html b/doc/SnakyHash/SymbolKeyed.html
index 1b37ba7..262180e 100644
--- a/doc/SnakyHash/SymbolKeyed.html
+++ b/doc/SnakyHash/SymbolKeyed.html
@@ -130,7 +130,7 @@ Overview
diff --git a/doc/SnakyHash/Version.html b/doc/SnakyHash/Version.html
index 06f72ef..e180a1c 100644
--- a/doc/SnakyHash/Version.html
+++ b/doc/SnakyHash/Version.html
@@ -111,7 +111,7 @@
diff --git a/doc/_index.html b/doc/_index.html
index d00c656..e8edf54 100644
--- a/doc/_index.html
+++ b/doc/_index.html
@@ -214,7 +214,7 @@ Namespace Listing A-Z
diff --git a/doc/file.CHANGELOG.html b/doc/file.CHANGELOG.html
index c381ba7..01a3bbf 100644
--- a/doc/file.CHANGELOG.html
+++ b/doc/file.CHANGELOG.html
@@ -147,7 +147,7 @@ Added
diff --git a/doc/file.CODE_OF_CONDUCT.html b/doc/file.CODE_OF_CONDUCT.html
index dbeb4e0..087744f 100644
--- a/doc/file.CODE_OF_CONDUCT.html
+++ b/doc/file.CODE_OF_CONDUCT.html
@@ -192,7 +192,7 @@ Attribution
diff --git a/doc/file.CONTRIBUTING.html b/doc/file.CONTRIBUTING.html
index 127b0bb..b7b6226 100644
--- a/doc/file.CONTRIBUTING.html
+++ b/doc/file.CONTRIBUTING.html
@@ -195,7 +195,7 @@ To release a new version:
diff --git a/doc/file.LICENSE.html b/doc/file.LICENSE.html
index 7440666..0b97bfa 100644
--- a/doc/file.LICENSE.html
+++ b/doc/file.LICENSE.html
@@ -60,7 +60,7 @@
The MIT License (MIT)
Copyright (c) 2022, 2025 Peter Boling
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
diff --git a/doc/file.README.html b/doc/file.README.html
index e374163..00a84ae 100644
--- a/doc/file.README.html
+++ b/doc/file.README.html
@@ -634,7 +634,7 @@ 🤑 One more thing
diff --git a/doc/file.SECURITY.html b/doc/file.SECURITY.html
index d2bfdcf..dbc8288 100644
--- a/doc/file.SECURITY.html
+++ b/doc/file.SECURITY.html
@@ -104,7 +104,7 @@ Snaky Hash for Enterprise
diff --git a/doc/index.html b/doc/index.html
index ca8eb47..5e5b3fb 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -634,7 +634,7 @@ 🤑 One more thing
diff --git a/doc/top-level-namespace.html b/doc/top-level-namespace.html
index f2e3075..c5467c4 100644
--- a/doc/top-level-namespace.html
+++ b/doc/top-level-namespace.html
@@ -100,7 +100,7 @@ Defined Under Namespace
diff --git a/spec/snaky_hash/snake_spec.rb b/spec/snaky_hash/snake_spec.rb
index e49540f..69b5825 100644
--- a/spec/snaky_hash/snake_spec.rb
+++ b/spec/snaky_hash/snake_spec.rb
@@ -34,117 +34,78 @@ class TheSnakedHash < Hashie::Mash
end
context "when serializer: true" do
- # Older versions of Ruby can't do Integer#to_sym,
- # which means snaky_hash on older rubies can't handle integer keys via new
- if RUBY_VERSION >= "3.0"
- context "when Ruby >= 3.0" do
- let(:snaky_klass) do
- klass = Class.new(Hashie::Mash) do
- include SnakyHash::Snake.new(
- key_type: :symbol, # default :string
- serializer: true, # default: false
- )
- end
+ let(:snaky_klass) do
+ klass = Class.new(Hashie::Mash) do
+ include SnakyHash::Snake.new(
+ key_type: :symbol, # default :string
+ serializer: true, # default: false
+ )
+ end
- klass.load_hash_extensions.add(:non_zero_keys_to_int) do |value|
- if value.is_a?(Hash)
- value.transform_keys do |key|
- key_int = key.to_s.to_i
- if key_int > 0
- key_int
- else
- key
- end
- end
+ klass.load_hash_extensions.add(:keys_are_based) do |value|
+ if value.is_a?(Hash)
+ value.keys.sort.each_with_index do |key, index|
+ key_int = key.to_s.to_i
+ ref = value.delete(key)
+ encoded_key = if key_int > 0
+ # See: https://idiosyncratic-ruby.com/4-what-the-pack.html#m0--base64-encoding-rfc-4648
+ "dog-#{index}"
else
- value
+ "cat-#{index}"
end
+ value[encoded_key] = ref
end
-
- klass
- end
-
- let(:snake) { snaky_klass.new(1 => "a", 0 => 4, "VeryFineHat" => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}) }
- let(:dump) { snaky_klass.dump(snake) }
- let(:hydrated) { snaky_klass.load(dump) }
-
- it "can initialize" do
- expect(snake).to eq({1 => "a", 0 => 4, :very_fine_hat => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}})
- end
-
- it "can dump" do
- expect(dump).to eq "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
- end
-
- it "can load" do
- expect(hydrated).to eq({1 => "a", :"0" => 4, :very_fine_hat => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}})
- end
-
- it "can access keys" do
- expect(hydrated["1"]).to be_nil
- expect(hydrated[1]).to eq("a")
- expect(hydrated["0"]).to eq(4)
- expect(hydrated[0]).to be_nil
- expect(hydrated.very_fine_hat).to eq({3 => "v", 5 => 7, :very_fine_hat => "feathers"})
- expect(hydrated.very_fine_hat.very_fine_hat).to eq("feathers")
- expect(hydrated.very_fine_hat[:very_fine_hat]).to eq("feathers")
- expect(hydrated.very_fine_hat["very_fine_hat"]).to eq("feathers")
end
+ value
end
- else
- context "when Ruby < 3.0" do
- let(:snaky_klass) do
- klass = Class.new(Hashie::Mash) do
- include SnakyHash::Snake.new(
- key_type: :symbol, # default :string
- serializer: true, # default: false
- )
- end
- klass.load_hash_extensions.add(:non_zero_keys_to_int) do |value|
- if value.is_a?(Hash)
- value.keys.each do |key|
- key_int = key.to_s.to_i
- if key_int > 0
- ref = value.delete(key)
- value[key_int] = ref
- end
- end
- else
- value
- end
- end
-
- klass
- end
+ klass
+ end
- let(:snake) { snaky_klass.new("1" => "a", "0" => 4, "VeryFineHat" => {"3" => "v", "5" => 7, :very_fine_hat => "feathers"}) }
- let(:dump) { snaky_klass.dump(snake) }
- let(:hydrated) { snaky_klass.load(dump) }
+ let(:snake) { snaky_klass.new("1" => "a", "0" => 4, "VeryFineHat" => {"3" => "v", "5" => 7, :very_fine_hat => "feathers"}) }
+ let(:dump) { snaky_klass.dump(snake) }
+ let(:hydrated) { snaky_klass.load(dump) }
- it "can initialize" do
- expect(snake).to eq({"1" => "a", "0" => 4, :very_fine_hat => {"3" => "v", "5" => 7, :very_fine_hat => "feathers"}})
- end
+ it "can initialize" do
+ expect(snake).to eq({"0": 4, "1": "a", very_fine_hat: {"3": "v", "5": 7, very_fine_hat: "feathers"}})
+ end
- it "can dump" do
- expect(dump).to eq "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
- end
+ it "can dump" do
+ expect(dump).to eq "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
+ end
- it "can load" do
- expect(hydrated).to eq({1 => "a", :"0" => 4, :very_fine_hat => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}})
- end
+ it "can load" do
+ expect(hydrated).to eq(
+ {
+ dog_1: "a",
+ cat_0: 4,
+ cat_2: {
+ dog_0: "v",
+ dog_1: 7,
+ cat_2: "feathers",
+ },
+ },
+ )
+ end
- it "can access keys" do
- expect(hydrated["1"]).to be_nil
- expect(hydrated[1]).to eq("a")
- expect(hydrated["0"]).to eq(4)
- expect(hydrated[0]).to be_nil
- expect(hydrated.very_fine_hat).to eq({3 => "v", 5 => 7, :very_fine_hat => "feathers"})
- expect(hydrated.very_fine_hat.very_fine_hat).to eq("feathers")
- expect(hydrated.very_fine_hat[:very_fine_hat]).to eq("feathers")
- expect(hydrated.very_fine_hat["very_fine_hat"]).to eq("feathers")
- end
- end
+ it "can access keys" do
+ expect(hydrated["1"]).to be_nil
+ expect(hydrated[1]).to be_nil
+ expect(hydrated["dog_1"]).to eq("a")
+ expect(hydrated[:dog_1]).to eq("a")
+ expect(hydrated.dog_1).to eq("a")
+ expect(hydrated["0"]).to be_nil
+ expect(hydrated[0]).to be_nil
+ expect(hydrated["cat_0"]).to eq(4)
+ expect(hydrated[:cat_0]).to eq(4)
+ expect(hydrated.cat_0).to eq(4)
+ expect(hydrated.very_fine_hat).to be_nil
+ expect(hydrated.cat_2).to eq({cat_2: "feathers", dog_0: "v", dog_1: 7})
+ expect(hydrated.cat_2.cat_2).to eq("feathers")
+ expect(hydrated.cat_2["cat_2"]).to eq("feathers")
+ expect(hydrated.cat_2[:cat_2]).to eq("feathers")
+ expect(hydrated.cat_2.dog_0).to eq("v")
+ expect(hydrated.cat_2.dog_1).to eq(7)
end
end
end