Skip to content

Instantly share code, notes, and snippets.

@daicoden
Created January 16, 2025 20:53
Show Gist options
  • Save daicoden/ba3640324cbd692e593290c97dc3cbe3 to your computer and use it in GitHub Desktop.
Save daicoden/ba3640324cbd692e593290c97dc3cbe3 to your computer and use it in GitHub Desktop.
module SerializeInterfaceStructs
extend T::Helpers
class Serialization < T::Struct
TYPE_FIELD_NAME = :__serialize_interface_structs_type__
const :__serialize_interface_structs_type__, String
const :value, T::Struct
sig { params(struct: T::Struct).returns(Serialization) }
def self.from_struct(struct)
Serialization.new(__serialize_interface_structs_type__: T.must(struct.class.name), value: struct)
end
sig { params(hash: T::Hash[String, Object]).returns(T::Struct) }
def self.from_serialized_hash(hash)
class_name = T.cast(hash[TYPE_FIELD_NAME.to_s], String)
class_name.constantize.from_hash(hash['value']) # rubocop:disable Sorbet/ConstantsFromStrings
end
sig { params(object: Object).returns(Object) }
def self.deserialize_or_original(object)
if object.is_a?(Hash) && seriailzed_by_me?(object)
from_serialized_hash(object)
elsif object.is_a?(Array)
object.map { |v| deserialize_or_original(v) }
else
object
end
end
sig { params(object: Object).returns(Object) }
def self.serialize_or_original(object)
if object.is_a?(T::Struct)
from_struct(object).serialize
elsif object.is_a?(Array)
object.map { |v| serialize_or_original(v) }
else
object
end
end
sig { params(hash: T::Hash[String, Object]).returns(T::Boolean) }
def self.seriailzed_by_me?(hash)
hash.key?(TYPE_FIELD_NAME.to_s)
end
end
requires_ancestor { T::Struct }
module ClassMethods
extend T::Generic
include Kernel
has_attached_class!
sig { params(hash: T::Hash[String, Object]).returns(T.attached_class) }
def from_hash(hash)
super(hash.transform_values { |v| Serialization.deserialize_or_original(v) })
end
end
extend T::Props::CustomType
mixes_in_class_methods(ClassMethods)
sig { params(strict: T::Boolean).returns(T::Hash[String, Object]) }
def serialize(strict = false)
super.transform_values { |v| Serialization.serialize_or_original(v) }
end
end
# typed: strict
# frozen_string_literal: true
require 'rails_helper'
module ValueStruct
RSpec.describe SerializeInterfaceStructs do
module TestStructs
module Interface
extend T::Helpers
end
class StructA < T::Struct
extend T::Helpers
include Interface
const :z, String
end
class StructB < T::Struct
include Interface
const :o, String
end
class SerializeMe < T::Struct
include SerializeInterfaceStructs
const :single, Interface
const :optional_single, T.any(Interface, T::Array[Interface])
const :array, T::Array[Interface]
const :any_structs, T.any(StructA, StructB)
const :any_array_structs, T.any(StructA, T::Array[Interface])
# const :nilable_single, T.nilable(T.all(SerializeInterfaceStruct, Interface))
# const :array, T::Array[T.all(SerializeInterfaceStruct, Interface)]
# const :nilable_array, T.nilable(T.all(SerializeInterfaceStruct, T::Array[Interface]))
end
class NestedSerializeMe < T::Struct
const :serialize_me, SerializeMe
end
end
it "is serializable" do
struct_a = TestStructs::StructA.new(z: "z")
struct_b = TestStructs::StructB.new(o: "o")
serialize_me = TestStructs::SerializeMe.new(
single: struct_a,
optional_single: struct_b,
array: [struct_a, struct_b],
any_structs: struct_b,
any_array_structs: [struct_a, struct_b],
)
serialized = JSON.parse(JSON.dump(serialize_me.serialize))
expect(TestStructs::SerializeMe.from_hash(serialized)).to eq(serialize_me)
end
it "is serializable with differnt any types" do
struct_a = TestStructs::StructA.new(z: "z")
struct_b = TestStructs::StructB.new(o: "o")
serialize_me = TestStructs::SerializeMe.new(
single: struct_a,
optional_single: [struct_a, struct_b],
array: [struct_a, struct_b],
any_structs: struct_b,
any_array_structs: struct_a,
)
serialized = JSON.parse(JSON.dump(serialize_me.serialize))
expect(TestStructs::SerializeMe.from_hash(serialized)).to eq(serialize_me)
end
it "works when serialized in another object" do
struct_a = TestStructs::StructA.new(z: "z")
struct_b = TestStructs::StructB.new(o: "o")
serialize_me = TestStructs::SerializeMe.new(
single: struct_a,
optional_single: [struct_a, struct_b],
array: [struct_a, struct_b],
any_structs: struct_b,
any_array_structs: struct_a,
)
nested_serialize_me = TestStructs::NestedSerializeMe.new(serialize_me:)
serialized = JSON.parse(JSON.dump(nested_serialize_me.serialize))
expect(TestStructs::NestedSerializeMe.from_hash(serialized)).to eq(nested_serialize_me)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment