Skip to content

Commit 7b194a8

Browse files
authored
Ordered structs (JuliaIO#224)
* OrderedDict support for MatlabClassObject * MatlabStructArray integer indexing
1 parent b71cce6 commit 7b194a8

File tree

8 files changed

+78
-34
lines changed

8 files changed

+78
-34
lines changed

Project.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
name = "MAT"
22
uuid = "23992714-dd62-5051-b70f-ba57cb901cac"
3-
version = "0.11.2"
3+
version = "0.11.3"
44

55
[deps]
6-
BufferedStreams = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
76
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
87
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
98
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
9+
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
1010
PooledArrays = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
1111
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
1212
StringEncodings = "69024149-9ee7-55f6-a4c4-859efe599b68"
1313
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
1414

1515
[compat]
16-
BufferedStreams = "0.4.1, 1"
1716
CodecZlib = "0.5, 0.6, 0.7"
1817
Dates = "1"
1918
HDF5 = "0.16, 0.17"
19+
OrderedCollections = "1"
2020
PooledArrays = "1.4.3"
2121
StringEncodings = "0.3.7"
2222
Tables = "1.12.1"

docs/src/object_arrays.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,34 +84,35 @@ Note that before v0.11 MAT.jl will read struct arrays as a Dict with concatenate
8484

8585
You can write an old class object with the `MatlabClassObject` and arrays of objects with `MatlabStructArray` by providing the class name. These are also the types you obtain when you read files.
8686

87+
> Please note that the order of the fields is important for MatlabClassObjects to be read properly in MATLAB. You may get `Warning: Fields of object 'tc' do not match the current constructor definition for class 'TestClass'. The object has been converted to a structure.`. Consider using `OrderedDict` if you have multiple fields.
88+
8789
Write a single class object:
8890
```julia
89-
d = Dict("foo" => 5.0)
90-
obj = MatlabClassObject(d, "TestClassOld")
91-
matwrite("matfile.mat", Dict("tc_old" => obj))
91+
d = Dict{String,Any}("foo" => 5.0)
92+
obj = MatlabClassObject(d, "TestClass")
93+
matwrite("matfile.mat", Dict("tc" => obj))
9294
```
9395

9496
A class object array
9597
```julia
96-
class_array = MatlabStructArray(["foo"], [[5.0, "bar"]], "TestClassOld")
98+
class_array = MatlabStructArray(["foo"], [[5.0, "bar"]], "TestClass")
9799
matwrite("matfile.mat", Dict("class_array" => class_array))
98100
```
99101

100102
Also a class object array, but will be converted to `MatlabStructArray` internally:
101103
```julia
102104
class_array = MatlabClassObject[
103-
MatlabClassObject(Dict("foo" => 5.0), "TestClassOld"),
104-
MatlabClassObject(Dict("foo" => "bar"), "TestClassOld")
105+
MatlabClassObject(Dict{String,Any}("foo" => 5.0), "TestClass"),
106+
MatlabClassObject(Dict{String,Any}("foo" => "bar"), "TestClass")
105107
]
106108
matwrite("matfile.mat", Dict("class_array" => class_array))
107109
```
108110

109111
A cell array:
110112
```julia
111113
cell_array = Any[
112-
MatlabClassObject(Dict("foo" => 5.0), "TestClassOld"),
113-
MatlabClassObject(Dict("a" => "bar"), "AnotherClass")
114+
MatlabClassObject(Dict{String,Any}("foo" => 5.0), "TestClass"),
115+
MatlabClassObject(Dict{String,Any}("a" => "bar"), "AnotherClass")
114116
]
115117
matwrite("matfile.mat", Dict("cell_array" => cell_array))
116118
```
117-

src/MAT_HDF5.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ function m_write(mfile::MatlabHDF5File, parent::HDF5Parent, name::String, arr::M
660660
else
661661
write_attribute(g, name_type_attr_matlab, arr.class)
662662
write_attribute(g, object_decode_attr_matlab, UInt32(2))
663+
write_attribute(g, "MATLAB_fields", HDF5.VLen(arr.names))
663664
end
664665
for (fieldname, field_values) in arr
665666
refs = _write_references!(mfile, parent, field_values)
@@ -699,7 +700,9 @@ function m_write(mfile::MatlabHDF5File, parent::HDF5Parent, name::String, obj::M
699700
try
700701
write_attribute(g, name_type_attr_matlab, obj.class)
701702
write_attribute(g, object_decode_attr_matlab, UInt32(2))
702-
for (ki, vi) in zip(keys(obj), values(obj))
703+
all_keys = collect(keys(obj))
704+
write_attribute(g, "MATLAB_fields", HDF5.VLen(all_keys))
705+
for (ki, vi) in zip(all_keys, values(obj))
703706
m_write(mfile, g, ki, vi)
704707
end
705708
finally

src/MAT_subsys.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ function set_fwrap_data!(subsys::Subsystem)
754754
push!(fwrap_data, reshape(subsys.mcos_class_alias_metadata, :, 1))
755755
push!(fwrap_data, reshape(subsys.prop_vals_defaults, :, 1))
756756

757-
fw_obj = MatlabOpaque(Dict("__filewrapper__" => reshape(fwrap_data, :, 1)), "FileWrapper__")
757+
fw_obj = MatlabOpaque(Dict{String,Any}("__filewrapper__" => reshape(fwrap_data, :, 1)), "FileWrapper__")
758758
return fw_obj
759759
end
760760

src/MAT_types.jl

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import Dates
3434
import Dates: DateTime, Second, Millisecond
3535
import PooledArrays: PooledArray, RefArray
3636
using Tables: Tables
37+
import OrderedCollections: OrderedDict
3738

3839
export MatlabStructArray, StructArrayField, convert_struct_array
3940
export MatlabClassObject
@@ -68,7 +69,7 @@ using MAT
6869
s_arr = MatlabStructArray(["a", "b"], [[1, 2],["foo", 5]])
6970
7071
# write-read
71-
matwrite("matfile.mat", Dict("struct_array" => s_arr))
72+
matwrite("matfile.mat", Dict{String,Any}("struct_array" => s_arr))
7273
read_s_arr = matread("matfile.mat")["struct_array"]
7374
7475
# convert to Dict Array
@@ -144,6 +145,8 @@ Base.haskey(arr::MatlabStructArray, k::AbstractString) = k in keys(arr)
144145
function Base.copy(arr::MatlabStructArray{N}) where {N}
145146
return MatlabStructArray{N}(copy(arr.names), copy(arr.values))
146147
end
148+
class(arr::MatlabStructArray) = arr.class
149+
class(d::AbstractDict) = ""
147150

148151
function Base.iterate(arr::T, i=next_state(arr)) where {T<:MatlabStructArray}
149152
if i == 0
@@ -255,6 +258,16 @@ function Base.Array{D,N}(arr::MatlabStructArray{N}) where {D<:AbstractDict,N}
255258
return result
256259
end
257260

261+
function Base.getindex(arr::MatlabStructArray, s::Integer)
262+
row_values = [getindex(v, s) for v in arr.values]
263+
if isempty(arr.class)
264+
D = Dict{String,Any}
265+
else
266+
D = OrderedDict{String,Any}
267+
end
268+
return create_struct(D, arr.names, row_values, arr.class)
269+
end
270+
258271
function create_struct(::Type{D}, keys, values, class::String) where {T,D<:AbstractDict{T}}
259272
return D(T.(keys) .=> values)
260273
end
@@ -284,7 +297,7 @@ Internal Marker for Empty Structs with dimensions like 1x0 or 0x0
284297
struct EmptyStruct
285298
dims::Vector{UInt64}
286299
end
287-
300+
class(m::EmptyStruct) = ""
288301

289302
"""
290303
MatlabClassObject(
@@ -296,11 +309,13 @@ Type to store old class objects. Inside MATLAB a class named \"TestClassOld\" wo
296309
297310
If you want to write these objects you have to make sure the keys in the Dict match the class defined properties/fields.
298311
"""
299-
struct MatlabClassObject <: AbstractDict{String,Any}
300-
d::Dict{String,Any}
312+
struct MatlabClassObject{D<:AbstractDict{String,Any}} <: AbstractDict{String,Any}
313+
d::D
301314
class::String
302315
end
303316

317+
class(m::MatlabClassObject) = m.class
318+
304319
Base.eltype(::Type{MatlabClassObject}) = Pair{String,Any}
305320
Base.length(m::MatlabClassObject) = length(m.d)
306321
Base.keys(m::MatlabClassObject) = keys(m.d)
@@ -320,7 +335,7 @@ function Base.isapprox(m1::MatlabClassObject, m2::MatlabClassObject; kwargs...)
320335
return m1.class == m2.class && dict_isapprox(m1.d, m2.d; kwargs...)
321336
end
322337

323-
function MatlabStructArray(arr::AbstractArray{MatlabClassObject})
338+
function MatlabStructArray(arr::AbstractArray{<:MatlabClassObject})
324339
first_obj, remaining_obj = Iterators.peel(arr)
325340
class = first_obj.class
326341
if !all(x -> isequal(class, x.class), remaining_obj)
@@ -331,7 +346,7 @@ function MatlabStructArray(arr::AbstractArray{MatlabClassObject})
331346
return MatlabStructArray(arr, class)
332347
end
333348

334-
function convert_struct_array(d::Dict{String,Any}, class::String="")
349+
function convert_struct_array(d::AbstractDict{String,Any}, class::String="")
335350
# there is no possibility of having cell arrays mixed with struct arrays (afaik)
336351
field_values = first(values(d))
337352
if field_values isa StructArrayField
@@ -351,13 +366,15 @@ function Base.Array(arr::MatlabStructArray{N}) where {N}
351366
if isempty(arr.class)
352367
return Array{Dict{String,Any},N}(arr)
353368
else
354-
return Array{MatlabClassObject,N}(arr)
369+
# ordered dict by default, to preserve field order
370+
D = OrderedDict{String,Any}
371+
return Array{MatlabClassObject{D},N}(arr)
355372
end
356373
end
357374

358-
function create_struct(::Type{D}, keys, values, class::String) where {D<:MatlabClassObject}
359-
d = Dict{String,Any}(string.(keys) .=> values)
360-
return MatlabClassObject(d, class)
375+
function create_struct(::Type{MatlabClassObject{D}}, keys, values, class::String) where {D<:AbstractDict}
376+
d = D(string.(keys) .=> values)
377+
return MatlabClassObject{D}(d, class)
361378
end
362379

363380
"""
@@ -370,10 +387,11 @@ Type to store opaque class objects.
370387
These are the 'modern' Matlab classes, different from the old `MatlabClassObject` types.
371388
372389
"""
373-
struct MatlabOpaque <: AbstractDict{String,Any}
374-
d::Dict{String,Any}
390+
struct MatlabOpaque{D<:AbstractDict{String,Any}} <: AbstractDict{String,Any}
391+
d::D
375392
class::String
376393
end
394+
class(m::MatlabOpaque) = m.class
377395

378396
Base.eltype(::Type{MatlabOpaque}) = Pair{String,Any}
379397
Base.length(m::MatlabOpaque) = length(m.d)
@@ -492,7 +510,7 @@ end
492510

493511
function MatlabOpaque(d::ScalarOrArray{DateTime})
494512
return MatlabOpaque(
495-
Dict(
513+
Dict{String,Any}(
496514
"tz" => "",
497515
"data" => map_or_not(to_matlab_data, d),
498516
"fmt" => "",
@@ -514,7 +532,7 @@ to_matlab_millis(d::Millisecond) = Float64(Dates.value(d))
514532

515533
function MatlabOpaque(d::ScalarOrArray{Millisecond})
516534
return MatlabOpaque(
517-
Dict(
535+
Dict{String,Any}(
518536
"millis" => map_or_not(to_matlab_millis, d),
519537
),
520538
"duration"

src/MAT_v4.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
# http://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf
2828

2929
module MAT_v4
30-
using BufferedStreams, HDF5, SparseArrays
30+
using HDF5, SparseArrays
3131
import Base: read, write, close
3232

3333
round_uint8(data) = round.(UInt8, data)

src/MAT_v5.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
# http://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf
2727

2828
module MAT_v5
29-
using CodecZlib, BufferedStreams, HDF5, SparseArrays
29+
using CodecZlib, HDF5, SparseArrays
3030
import Base: read, write, close
3131
import ..MAT_types: MatlabStructArray, MatlabClassObject, MatlabTable
3232

test/types.jl

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ using Dates
6464
# class object array conversion
6565
s_arr_class = MatlabStructArray(d_arr, "TestClass")
6666
c_arr = Array(s_arr_class)
67-
@test c_arr isa Array{MatlabClassObject}
67+
@test c_arr isa Array{<:MatlabClassObject}
6868
@test all(c->c.class=="TestClass", c_arr)
6969
@test MatlabStructArray(c_arr) == s_arr_class
7070
@test s_arr_class != s_arr
@@ -78,6 +78,28 @@ using Dates
7878
@test_throws ErrorException(msg) MatlabStructArray(wrong_sarr)
7979
end
8080

81+
@testset "MatlabStructArray integer indexing" begin
82+
d_arr = Dict{String, Any}[
83+
Dict("x"=>[1.0,2.0], SubString("y")=>3.0),
84+
Dict("x"=>[5.0,6.0], "y"=>[])
85+
]
86+
s_arr = MatlabStructArray(d_arr)
87+
@test s_arr[1] == Array(s_arr)[1]
88+
@test s_arr[2] == Array(s_arr)[2]
89+
90+
s_arr = MatlabStructArray(reshape(d_arr, 1, 2))
91+
@test s_arr[1] == Array(s_arr)[1]
92+
@test s_arr[2] == Array(s_arr)[2]
93+
94+
s_arr = MatlabStructArray(reshape(d_arr, 2, 1))
95+
@test s_arr[1] == Array(s_arr)[1]
96+
@test s_arr[2] == Array(s_arr)[2]
97+
98+
s_arr = MatlabStructArray(reshape(d_arr, 2, 1), "TestClass")
99+
@test s_arr[1] == Array(s_arr)[1]
100+
@test s_arr[2] == Array(s_arr)[2]
101+
end
102+
81103
@testset "MatlabClassObject" begin
82104
d = Dict{String,Any}("a" => 5)
83105
obj = MatlabClassObject(d, "TestClassOld")
@@ -189,7 +211,7 @@ end
189211
end
190212

191213
@testset "MatlabOpaque duration" begin
192-
d = Dict(
214+
d = Dict{String,Any}(
193215
"millis" => [3.6e6 7.2e6],
194216
# "fmt" => 'h' # optional format
195217
)
@@ -198,7 +220,7 @@ end
198220
@test ms == map(Millisecond, d["millis"])
199221
@test MatlabOpaque(ms) == obj
200222

201-
d = Dict(
223+
d = Dict{String,Any}(
202224
"millis" => 12000.0,
203225
# "fmt" => 'h',
204226
)
@@ -209,7 +231,7 @@ end
209231
end
210232

211233
@testset "MatlabOpaque categorical" begin
212-
d = Dict(
234+
d = Dict{String,Any}(
213235
"isProtected" => false,
214236
"codes" => reshape(UInt8[0x02, 0x03, 0x01, 0x01, 0x01, 0x02], 3, 2),
215237
"categoryNames" => Any["Fair"; "Good"; "Poor";;],

0 commit comments

Comments
 (0)