Skip to content

Commit ad6c8fb

Browse files
committed
OrderedDict support for MatlabClassObject
1 parent b71cce6 commit ad6c8fb

File tree

8 files changed

+45
-33
lines changed

8 files changed

+45
-33
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@ uuid = "23992714-dd62-5051-b70f-ba57cb901cac"
33
version = "0.11.2"
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.8.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: 22 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
@@ -284,7 +287,7 @@ Internal Marker for Empty Structs with dimensions like 1x0 or 0x0
284287
struct EmptyStruct
285288
dims::Vector{UInt64}
286289
end
287-
290+
class(m::EmptyStruct) = ""
288291

289292
"""
290293
MatlabClassObject(
@@ -296,11 +299,13 @@ Type to store old class objects. Inside MATLAB a class named \"TestClassOld\" wo
296299
297300
If you want to write these objects you have to make sure the keys in the Dict match the class defined properties/fields.
298301
"""
299-
struct MatlabClassObject <: AbstractDict{String,Any}
300-
d::Dict{String,Any}
302+
struct MatlabClassObject{D<:AbstractDict{String,Any}} <: AbstractDict{String,Any}
303+
d::D
301304
class::String
302305
end
303306

307+
class(m::MatlabClassObject) = m.class
308+
304309
Base.eltype(::Type{MatlabClassObject}) = Pair{String,Any}
305310
Base.length(m::MatlabClassObject) = length(m.d)
306311
Base.keys(m::MatlabClassObject) = keys(m.d)
@@ -320,7 +325,7 @@ function Base.isapprox(m1::MatlabClassObject, m2::MatlabClassObject; kwargs...)
320325
return m1.class == m2.class && dict_isapprox(m1.d, m2.d; kwargs...)
321326
end
322327

323-
function MatlabStructArray(arr::AbstractArray{MatlabClassObject})
328+
function MatlabStructArray(arr::AbstractArray{<:MatlabClassObject})
324329
first_obj, remaining_obj = Iterators.peel(arr)
325330
class = first_obj.class
326331
if !all(x -> isequal(class, x.class), remaining_obj)
@@ -331,7 +336,7 @@ function MatlabStructArray(arr::AbstractArray{MatlabClassObject})
331336
return MatlabStructArray(arr, class)
332337
end
333338

334-
function convert_struct_array(d::Dict{String,Any}, class::String="")
339+
function convert_struct_array(d::AbstractDict{String,Any}, class::String="")
335340
# there is no possibility of having cell arrays mixed with struct arrays (afaik)
336341
field_values = first(values(d))
337342
if field_values isa StructArrayField
@@ -351,13 +356,15 @@ function Base.Array(arr::MatlabStructArray{N}) where {N}
351356
if isempty(arr.class)
352357
return Array{Dict{String,Any},N}(arr)
353358
else
354-
return Array{MatlabClassObject,N}(arr)
359+
# ordered dict by default, to preserve field order
360+
D = OrderedDict{String,Any}
361+
return Array{MatlabClassObject{D},N}(arr)
355362
end
356363
end
357364

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)
365+
function create_struct(::Type{MatlabClassObject{D}}, keys, values, class::String) where {D<:AbstractDict}
366+
d = D(string.(keys) .=> values)
367+
return MatlabClassObject{D}(d, class)
361368
end
362369

363370
"""
@@ -370,10 +377,11 @@ Type to store opaque class objects.
370377
These are the 'modern' Matlab classes, different from the old `MatlabClassObject` types.
371378
372379
"""
373-
struct MatlabOpaque <: AbstractDict{String,Any}
374-
d::Dict{String,Any}
380+
struct MatlabOpaque{D<:AbstractDict{String,Any}} <: AbstractDict{String,Any}
381+
d::D
375382
class::String
376383
end
384+
class(m::MatlabOpaque) = m.class
377385

378386
Base.eltype(::Type{MatlabOpaque}) = Pair{String,Any}
379387
Base.length(m::MatlabOpaque) = length(m.d)
@@ -492,7 +500,7 @@ end
492500

493501
function MatlabOpaque(d::ScalarOrArray{DateTime})
494502
return MatlabOpaque(
495-
Dict(
503+
Dict{String,Any}(
496504
"tz" => "",
497505
"data" => map_or_not(to_matlab_data, d),
498506
"fmt" => "",
@@ -514,7 +522,7 @@ to_matlab_millis(d::Millisecond) = Float64(Dates.value(d))
514522

515523
function MatlabOpaque(d::ScalarOrArray{Millisecond})
516524
return MatlabOpaque(
517-
Dict(
525+
Dict{String,Any}(
518526
"millis" => map_or_not(to_matlab_millis, d),
519527
),
520528
"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: 4 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
@@ -189,7 +189,7 @@ end
189189
end
190190

191191
@testset "MatlabOpaque duration" begin
192-
d = Dict(
192+
d = Dict{String,Any}(
193193
"millis" => [3.6e6 7.2e6],
194194
# "fmt" => 'h' # optional format
195195
)
@@ -198,7 +198,7 @@ end
198198
@test ms == map(Millisecond, d["millis"])
199199
@test MatlabOpaque(ms) == obj
200200

201-
d = Dict(
201+
d = Dict{String,Any}(
202202
"millis" => 12000.0,
203203
# "fmt" => 'h',
204204
)
@@ -209,7 +209,7 @@ end
209209
end
210210

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

0 commit comments

Comments
 (0)