JuliaIO / JLD2.jl

HDF5-compatible file format in pure Julia
Other
547 stars 85 forks source link

custom serialization through NamedTuples #442

Closed dpinol closed 8 months ago

dpinol commented 1 year ago

Should custom serialization be able to serialize though NamedTuple's?

Base.@kwdef mutable struct T
    f
end

const FS = NamedTuple

JLD2.writeas(::Type{T}) = FS

function JLD2.wconvert(::Type{FS}, t::T)
    @info "saving $(typeof(t))"
    return (f: t.f,)
end
function JLD2.rconvert(::Type{T}, fs::FS)
    @info "loading $T"
    return  T(; fs...)
end

t1 = T(1)
save_object("kk.jld2", t1)

I get this error

ERROR: MethodError: no method matching hasdata(::Type{NamedTuple})
Closest candidates are:
  hasdata(::DataType) at ~/.julia/packages/JLD2/1YVED/src/data/writing_datatypes.jl:38
  hasdata(::DataType, ::Any) at ~/.julia/packages/JLD2/1YVED/src/data/writing_datatypes.jl:38
Stacktrace:
  [1] odr(#unused#::Type{NamedTuple})
    @ JLD2 ~/.julia/packages/JLD2/1YVED/src/data/writing_datatypes.jl:592
  [2] objodr
    @ ~/.julia/packages/JLD2/1YVED/src/data/writing_datatypes.jl:119 [inlined]
  [3] write_dataset(f::JLD2.JLDFile{JLD2.MmapIO}, x::T, wsession::JLD2.JLDWriteSession{Dict{UInt64, JLD2.RelOffset}})
    @ JLD2 ~/.julia/packages/JLD2/1YVED/src/datasets.jl:645
  [4] write(g::JLD2.Group{JLD2.JLDFile{JLD2.MmapIO}}, name::String, obj::T, wsession::JLD2.JLDWriteSession{Dict{UInt64, JLD2.RelOffset}}; compress::Nothing)
    @ JLD2 ~/.julia/packages/JLD2/1YVED/src/compression.jl:138
  [5] write(g::JLD2.Group{JLD2.JLDFile{JLD2.MmapIO}}, name::String, obj::T, wsession::JLD2.JLDWriteSession{Dict{UInt64, JLD2.RelOffset}}) (repeats 2 times)
    @ JLD2 ~/.julia/packages/JLD2/1YVED/src/compression.jl:126
  [6] setindex!(g::JLD2.Group{JLD2.JLDFile{JLD2.MmapIO}}, obj::T, name::String)
    @ JLD2 ~/.julia/packages/JLD2/1YVED/src/groups.jl:125
  [7] setindex!(f::JLD2.JLDFile{JLD2.MmapIO}, obj::T, name::String)
    @ JLD2 ~/.julia/packages/JLD2/1YVED/src/JLD2.jl:461
  [8] (::JLD2.var"#77#78"{T})(file::JLD2.JLDFile{JLD2.MmapIO})
    @ JLD2 ~/.julia/packages/JLD2/1YVED/src/loadsave.jl:186
  [9] jldopen(::Function, ::String, ::Vararg{String}; kws::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ JLD2 ~/.julia/packages/JLD2/1YVED/src/loadsave.jl:4
 [10] jldopen(::Function, ::String, ::String)
    @ JLD2 ~/.julia/packages/JLD2/1YVED/src/loadsave.jl:1
 [11] save_object(filename::String, x::T)
    @ JLD2 ~/.julia/packages/JLD2/1YVED/src/loadsave.jl:185
 [12] top-level scope
    @ REPL[25]:1

thanks

JonasIsensee commented 1 year ago

Hi, this should be possible.

Note that NamedTuple is a UnionAll and not a DataType. This is probably the problem. If writeas constructs the proper NamedTuple type, it should work.

Still, you shouldn't (need to) do this... If things go right, this conversion method would not change the stored data (only the type metadata).

JonasIsensee commented 1 year ago

There are some constraints:

const FS = NamedTuple

JLD2.writeas(::Type{T1}) = NamedTuple{(fieldnames(T1)...,), Tuple{fieldtypes(T1)...}}

function JLD2.wconvert(::Type{<:FS}, t::T1) @info "saving $(typeof(t))"

have to explicitly construct named tuple type since T1 has an Any field

# and the (f=t.f) constructor will always check for instance types
return NamedTuple{(fieldnames(T1)...,), Tuple{fieldtypes(T1)...}}(t.f)

end

function JLD2.rconvert(::Type{T1}, fs::FS) @info "loading $T1" return T1(; fs...) end