diff --git a/src/StructTypes.jl b/src/StructTypes.jl index 8baf61d..e1b7325 100644 --- a/src/StructTypes.jl +++ b/src/StructTypes.jl @@ -933,7 +933,8 @@ and respecting various serialization/deserialization names, keyword args, etc. Most typical use-case is construct a custom type `T` from an `obj::AbstractDict`, but `constructfrom` is fully generic, so the inverse is also supported (turning any custom -struct into an `AbstractDict`). For example, an external service may be providing JSON +struct into an `AbstractDict`). A `CustomStruct` source is converted through its +`lower` method. For example, an external service may be providing JSON data with an evolving schema; as opposed to trying a strict "typed parsing" like `JSON3.read(json, T)`, it may be preferrable to setup a local custom struct with just the desired properties and call `StructTypes.constructfrom(T, JSON3.read(json))`. This @@ -1007,6 +1008,9 @@ constructfrom(::DictType, ::Type{T}, obj) where {T <: AbstractDict} = @inline constructfrom(::DictType, ::Type{T}, ::Type{K}, ::Type{V}, obj::S) where {T, K, V, S} = constructfrom(DictType(), T, K, V, StructType(S), obj) +constructfrom(::DictType, ::Type{T}, ::Type{K}, ::Type{V}, ::CustomStruct, obj) where {T, K, V} = + constructfrom(DictType(), T, K, V, lower(obj)) + function constructfrom(::DictType, ::Type{T}, ::Type{K}, ::Type{V}, ::DictType, obj) where {T, K, V} d = Dict{K, V}() for (k, v) in keyvaluepairs(obj) @@ -1068,6 +1072,9 @@ function constructfrom!(::Mutable, x::T, ::Union{Struct, Mutable}, obj) where {T return x end +constructfrom!(::Mutable, x::T, ::CustomStruct, obj) where {T} = + constructfrom!(x, lower(obj)) + constructfrom(::Struct, ::Type{T}, obj::S) where {T, S} = constructfrom(Struct(), T, StructType(S), obj) @@ -1102,6 +1109,9 @@ end constructfrom(::Struct, ::Type{T}, ::Union{Struct, Mutable}, obj) where {T} = construct(StructClosure(obj), T) +constructfrom(::Struct, ::Type{T}, ::CustomStruct, obj) where {T} = + constructfrom(T, lower(obj)) + struct TupleClosure{T} obj::T end diff --git a/test/runtests.jl b/test/runtests.jl index 8712567..adf3c57 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -877,3 +877,42 @@ end @testset "struct type on complex" begin @test StructTypes.StructType(ComplexF64) == StructTypes.Struct() end + +struct CustomColumn{T, S} + values::Vector{T} +end +StructTypes.StructType(::Type{<:CustomColumn}) = StructTypes.CustomStruct() +StructTypes.lowertype(::Type{<:CustomColumn}) = Dict{String, Any} +StructTypes.lower(c::CustomColumn{T, S}) where {T, S} = Dict{String, Any}( + "type" => string(T), "storage" => string(S), "values" => c.values) + +mutable struct MutableColumn + type::String + values::Vector{Int} + MutableColumn() = new() +end +StructTypes.StructType(::Type{MutableColumn}) = StructTypes.Mutable() + +struct ImmutableColumn + type::String + values::Vector{Int} +end +StructTypes.StructType(::Type{ImmutableColumn}) = StructTypes.Struct() + +struct LowersToNamedTuple + x::NamedTuple +end +StructTypes.StructType(::Type{LowersToNamedTuple}) = StructTypes.CustomStruct() +StructTypes.lower(x::LowersToNamedTuple) = x.x + +@testset "constructfrom with CustomStruct sources" begin + c = CustomColumn{Int, Nothing}([1, 2]) + @test StructTypes.constructfrom(Dict{String, Any}, c) == + Dict("type" => string(Int), "storage" => "Nothing", "values" => [1, 2]) + @test StructTypes.constructfrom(Dict{Symbol, Any}, c)[:storage] == "Nothing" + w = LowersToNamedTuple((type="Int64", values=[1, 2])) + m = StructTypes.constructfrom(MutableColumn, w) + @test (m.type, m.values) == ("Int64", [1, 2]) + s = StructTypes.constructfrom(ImmutableColumn, w) + @test (s.type, s.values) == ("Int64", [1, 2]) +end