diff --git a/.gitignore b/.gitignore index b47a502..4fdae3c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ /tmp/ *.gem .DS_Store +.idea/* diff --git a/lib/sorbet-schema/hash_transformer.rb b/lib/sorbet-schema/hash_transformer.rb index d0a0006..3a847b9 100644 --- a/lib/sorbet-schema/hash_transformer.rb +++ b/lib/sorbet-schema/hash_transformer.rb @@ -6,17 +6,40 @@ class HashTransformer extend T::Sig + sig { params(should_serialize_values: T::Boolean).void } + def initialize(should_serialize_values: false) + @should_serialize_values = should_serialize_values + end + sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T::Hash[Symbol, T.untyped]) } def deep_symbolize_keys(hash) hash.each_with_object({}) do |(key, value), result| - result[key.to_sym] = value.is_a?(Hash) ? deep_symbolize_keys(value) : value + result[key.to_sym] = transform_value(value, hash_transformation_method: :deep_symbolize_keys) end end sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T::Hash[String, T.untyped]) } def deep_stringify_keys(hash) hash.each_with_object({}) do |(key, value), result| - result[key.to_s] = value.is_a?(Hash) ? deep_stringify_keys(value) : value + result[key.to_s] = transform_value(value, hash_transformation_method: :deep_stringify_keys) + end + end + + private + + sig { returns(T::Boolean) } + attr_reader :should_serialize_values + + sig { params(value: T.untyped, hash_transformation_method: Symbol).returns(T.untyped) } + def transform_value(value, hash_transformation_method:) + if value.is_a?(Hash) + send(hash_transformation_method, value) + elsif value.is_a?(Array) + value.map { |inner_val| transform_value(inner_val, hash_transformation_method: hash_transformation_method) } + elsif value.respond_to?(:serialize) && should_serialize_values + value.serialize + else + value end end end diff --git a/lib/typed/hash_serializer.rb b/lib/typed/hash_serializer.rb index bdd5b09..f642c38 100644 --- a/lib/typed/hash_serializer.rb +++ b/lib/typed/hash_serializer.rb @@ -8,14 +8,26 @@ class HashSerializer < Serializer Input = type_member { {fixed: InputHash} } Output = type_member { {fixed: OutputHash} } + sig { params(schema: Schema, should_serialize_values: T::Boolean).void } + def initialize(schema:, should_serialize_values: false) + @should_serialize_values = should_serialize_values + + super(schema: schema) + end + sig { override.params(source: Input).returns(Result[T::Struct, DeserializeError]) } def deserialize(source) - deserialize_from_creation_params(HashTransformer.new.deep_symbolize_keys(source)) + deserialize_from_creation_params(HashTransformer.new(should_serialize_values: should_serialize_values).deep_symbolize_keys(source)) end sig { override.params(struct: T::Struct).returns(Output) } def serialize(struct) - HashTransformer.new.deep_symbolize_keys(struct.serialize) + HashTransformer.new(should_serialize_values: should_serialize_values).deep_symbolize_keys(struct.serialize) end + + private + + sig { returns(T::Boolean) } + attr_reader :should_serialize_values end end diff --git a/test/hash_transformer_test.rb b/test/hash_transformer_test.rb index 4fd89ff..a4093f6 100644 --- a/test/hash_transformer_test.rb +++ b/test/hash_transformer_test.rb @@ -2,14 +2,16 @@ class HashTransformerTest < Minitest::Test def setup - # standard:disable Style/HashSyntax @test_hash = { - "test" => 1, - another: 1, - deeper: { - anotherhash: 2, + "test" => TestEnums::EnumOne, + :another => 1, + :deeper => { + :anotherhash => 2, "deeperagain" => { - "value" => 3 + "value" => TestEnums::EnumThree, + "boolean" => false, + "date" => Date.new(1776, 7, 4), + "array" => [1, TestEnums::EnumOne, {"verydeep" => 1}] } } } @@ -19,12 +21,15 @@ def setup def test_deep_symbolize_keys_symbolizes_all_keys expected_hash = { - test: 1, + test: TestEnums::EnumOne, another: 1, deeper: { anotherhash: 2, deeperagain: { - value: 3 + value: TestEnums::EnumThree, + boolean: false, + date: Date.new(1776, 7, 4), + array: [1, TestEnums::EnumOne, {verydeep: 1}] } } } @@ -32,18 +37,61 @@ def test_deep_symbolize_keys_symbolizes_all_keys assert_equal(expected_hash, @transformer.deep_symbolize_keys(@test_hash)) end + def test_deep_symbolize_keys_serialize_values_symbolizes_all_keys_and_serializes_values + transformer = HashTransformer.new(should_serialize_values: true) + + expected_hash = { + test: "1", + another: 1, + deeper: { + anotherhash: 2, + deeperagain: { + value: "3", + boolean: false, + date: Date.new(1776, 7, 4), + array: [1, "1", {verydeep: 1}] + } + } + } + + assert_equal(expected_hash, transformer.deep_symbolize_keys(@test_hash)) + end + def test_deep_stringify_keys_stringifies_all_keys expected_hash = { - "test" => 1, + "test" => TestEnums::EnumOne, "another" => 1, "deeper" => { "anotherhash" => 2, "deeperagain" => { - "value" => 3 + "value" => TestEnums::EnumThree, + "boolean" => false, + "date" => Date.new(1776, 7, 4), + "array" => [1, TestEnums::EnumOne, {"verydeep" => 1}] } } } assert_equal(expected_hash, @transformer.deep_stringify_keys(@test_hash)) end + + def test_deep_stringify_keys_serialize_values_stringifies_all_keys_and_serializes_values + transformer = HashTransformer.new(should_serialize_values: true) + + expected_hash = { + "test" => "1", + "another" => 1, + "deeper" => { + "anotherhash" => 2, + "deeperagain" => { + "value" => "3", + "boolean" => false, + "date" => Date.new(1776, 7, 4), + "array" => [1, "1", {"verydeep" => 1}] + } + } + } + + assert_equal(expected_hash, transformer.deep_stringify_keys(@test_hash)) + end end diff --git a/test/support/test_enums.rb b/test/support/test_enums.rb new file mode 100644 index 0000000..45ae64b --- /dev/null +++ b/test/support/test_enums.rb @@ -0,0 +1,9 @@ +# typed: true + +class TestEnums < T::Enum + enums do + EnumOne = new("1") + EnumTwo = new("2") + EnumThree = new("3") + end +end