diff --git a/src/BSON.jl b/src/BSON.jl index 9d7cbd0..6da655f 100644 --- a/src/BSON.jl +++ b/src/BSON.jl @@ -25,6 +25,7 @@ end function applychildren!(f::Function, x::BSONArray)::BSONArray for i = 1:length(x) + isassigned(x, i) || continue x[i] = f(x[i]) end return x diff --git a/src/extensions.jl b/src/extensions.jl index 3875903..08a9516 100644 --- a/src/extensions.jl +++ b/src/extensions.jl @@ -67,18 +67,37 @@ lower(x::Vector{UInt8}) = x reinterpret_(::Type{T}, x) where T = T[_x for _x in reinterpret(T, x)] -function lower(x::Array) - ndims(x) == 1 && !isbitstype(eltype(x)) && return Any[x...] - BSONDict(:tag => "array", :type => eltype(x), :size => Any[size(x)...], - :data => isbitstype(eltype(x)) ? reinterpret_(UInt8, reshape(x, :)) : Any[x...]) + +function collect_any(xs) + ys = Vector{Any}(undef, length(xs)) + for i = 1:length(xs) + isassigned(xs, i) && (ys[i] = xs[i]) + end + return ys +end + + +function lower(x::Array{T,N}) where {T,N} + isone(N) && !isbitstype(T) && return collect_any(x) + BSONDict(:tag => "array", :type => T, :size => Any[size(x)...], + :data => isbitstype(T) ? reinterpret_(UInt8, reshape(x, :)) : collect_any(x)) end + tags[:array] = d -> - isbitstype(d[:type]) ? - sizeof(d[:type]) == 0 ? - fill(d[:type](), d[:size]...) : - reshape(reinterpret_(d[:type], d[:data]), map(normalize_typeparams, d[:size])...) : - Array{d[:type]}(reshape(d[:data], d[:size]...)) +if isbitstype(d[:type]) + if sizeof(d[:type]) == 0 + fill(d[:type](), d[:size]...) + else + reshape(reinterpret_(d[:type], d[:data]), d[:size]...) + end + else + a = Array{d[:type]}(undef, d[:size]...) + for i in 1:length(d[:data]) + isassigned(d[:data], i) && (a[i] = d[:data][i]) + end + a + end # Structs diff --git a/src/read.jl b/src/read.jl index bd3177c..3ec1583 100644 --- a/src/read.jl +++ b/src/read.jl @@ -32,15 +32,21 @@ end function parse_array(io::IO)::BSONArray len = read(io, Int32) ps = BSONArray() - + len_set = false while (tag = read(io, BSONType)) ≠ eof # Note that arrays are dicts with the index as the key - while read(io, UInt8) != 0x00 - nothing - end - push!(ps, parse_tag(io::IO, tag)) + # The first index in dict is "length" => length(x) + index_or_length = parse_cstr(io) + val = parse_tag(io::IO, tag) + if index_or_length == "length" + resize!(ps, Int(val)) + len_set = true + else + i = Base.parse(Int, index_or_length) + 1 + len_set || resize!(ps, i) + ps[i] = val + end end - ps end diff --git a/src/write.jl b/src/write.jl index 0f7e086..b90f80f 100644 --- a/src/write.jl +++ b/src/write.jl @@ -35,7 +35,7 @@ end bson_primitive(io::IO, doc::BSONDict) = bson_doc(io, doc) bson_primitive(io::IO, x::BSONArray) = - bson_doc(io, [Base.string(i-1) => v for (i, v) in enumerate(x)]) + bson_doc(io, vcat(["length" => length(x)],[Base.string(i-1) => x[i] for i = 1:length(x) if isassigned(x, i)])) # Lowering diff --git a/test/runtests.jl b/test/runtests.jl index d5d6c41..2ef3247 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,29 @@ using BSON using Test roundtrip_equal(x) = BSON.roundtrip(x) == x +function roundtrip_equal(x::AbstractArray) + result = BSON.roundtrip(x) + all(eachindex(result, x)) do I + if !isassigned(x, I) + !isassigned(result, I) + else + x[I] == result[I] + end + end +end + +function roundtrip_equal(x::Dict{String,Array{Real,2}}) + result = BSON.roundtrip(x) + keys(result) == keys(x) + all(keys(x)) do key + if isassigned(x[key]) + x[key] == result[key] + else + isassigned(x[key]) == isassigned(result[key]) + end + end +end + mutable struct Foo x @@ -33,6 +56,8 @@ end @test roundtrip_equal(Array) @test roundtrip_equal([1,2,3]) @test roundtrip_equal(rand(2,3)) + @test roundtrip_equal(Array{Real}(undef, 2,3)) + @test roundtrip_equal(Array{String}(undef, 2,3)) @test roundtrip_equal(Array{Real}(rand(2,3))) @test roundtrip_equal(1+2im) @test roundtrip_equal(Nothing[]) @@ -41,6 +66,7 @@ end @test roundtrip_equal(fill(S(), (1,3))) @test roundtrip_equal(Set([1,2,3])) @test roundtrip_equal(Dict("a"=>1)) + @test roundtrip_equal(Dict("a"=>Array{Real}(undef, 2,3))) @test roundtrip_equal(T(())) end