Skip to content

Commit 685b5f9

Browse files
authored
Merge pull request #937 from JuliaRobotics/23Q1/srz/transcodetypeunmarshal
add transcodeType util for super unmarshal
2 parents 80a4758 + 3740ede commit 685b5f9

File tree

4 files changed

+211
-2
lines changed

4 files changed

+211
-2
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Unmarshal = "0.4"
5151
julia = "1.6"
5252

5353
[extras]
54+
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
5455
GraphPlot = "a2cc645c-3eea-5389-862e-a155d0052231"
5556
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
5657
Manifolds = "1cead3c2-87b3-11e9-0ccd-23c62b72b94e"
@@ -59,4 +60,4 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
5960
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
6061

6162
[targets]
62-
test = ["Test", "GraphPlot", "LinearAlgebra", "Manifolds", "Pkg", "Statistics"]
63+
test = ["Test", "DataStructures", "GraphPlot", "LinearAlgebra", "Manifolds", "Pkg", "Statistics"]

src/services/Serialization.jl

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,106 @@ function getTypeFromSerializationModule(_typeString::AbstractString)
117117
nothing
118118
end
119119

120+
## =============================================================================
121+
## Transcoding / Unmarshal types helper
122+
## =============================================================================
123+
124+
"""
125+
$SIGNATURES
126+
Should be a highly reusable function for any transcoding of intermediate type (or dict) to a desired output type.
127+
128+
examples
129+
```julia
130+
Base.@kwdef struct HardType
131+
name::String
132+
time::DateTime = now(UTC)
133+
val::Float64 = 0.0
134+
end
135+
136+
# slight human overhead for each type to ignore extraneous field construction
137+
# TODO, devnote drop this requirement with filter of _names in transcodeType
138+
HardType(;
139+
name::String,
140+
time::DateTime = now(UTC),
141+
val::Float64 = 0.0,
142+
ignorekws...
143+
) = HardType(name,time,val)
144+
145+
# somehow one gets an intermediate type
146+
imt = IntermediateType(
147+
v"1.0",
148+
"NotUsedYet",
149+
"test",
150+
now(UTC),
151+
1.0
152+
)
153+
# or dict (testing string keys)
154+
imd = Dict(
155+
"_version" => v"1.0",
156+
"_type" => "NotUsedYet",
157+
"name" => "test",
158+
"time" => now(UTC),
159+
"val" => 1.0
160+
)
161+
# ordered dict (testing symbol keys)
162+
iod = OrderedDict(
163+
:_version => v"1.0",
164+
:_type => "NotUsedYet",
165+
:name => "test",
166+
:time => now(UTC),
167+
# :val => 1.0
168+
)
169+
170+
# do the transcoding to a slighly different hard type
171+
T1 = transcodeType(HardType, imt)
172+
T2 = transcodeType(HardType, imd)
173+
T3 = transcodeType(HardType, iod)
174+
```
175+
"""
176+
function transcodeType(
177+
::Type{T},
178+
inObj
179+
) where T
180+
#
181+
# specializations as inner functions (don't have to be inners)
182+
# these few special cases came up with examples below, note recursions
183+
_instance(S::Type, x) = S(x)
184+
_instance(_::Type{S}, x::S) where S = x # if ambiguous, delete and do alternative `_instance(S::Type, x) = S===Any ? x : S(x)`
185+
_instance(S::Type{I}, x::AbstractString) where I <: Number = Base.parse(I, x)
186+
_instance(S::Type{E}, x::AbstractVector) where E <: AbstractVector = _instance.(eltype(E),x)
187+
_instance(S::Type{<:AbstractDict{K,V}}, x::AbstractDict) where {K,V} = (tup=(Symbol.(keys(x)) .=> _instance.(V,values(x)) ) ; S(tup...) )
188+
189+
# what the struct wants
190+
_types = fieldtypes(T)
191+
_names = fieldnames(T)
192+
# (closure) resolve random ordering problem
193+
_getIdx(s::Symbol) = findfirst(x->x==s, _names)
194+
# (closure) create an instance of a field
195+
makething(k::Symbol, v) = begin
196+
idx = _getIdx(k)
197+
if !isnothing(idx)
198+
# this field is in the output type and should be included
199+
k => _instance(_types[_getIdx(k)], v)
200+
else
201+
# this field should be ignored in the output type
202+
Symbol(:VOID_,rand(1:1000000)) => nothing
203+
end
204+
end
205+
# zip together keys/fields and values for either dict or intermediate type
206+
_srckeys(s::AbstractDict) = keys(s)
207+
_srckeys(s) = fieldnames(typeof(s))
208+
_srcvals(s::AbstractDict) = values(s)
209+
_srcvals(s) = map(k->getproperty(s,k), _srckeys(s))
210+
# NOTE, improvement, filter extraneous fields not in _names
211+
arr = [makething(Symbol(k),v) for (k,v) in zip(_srckeys(inObj),_srcvals(inObj))]
212+
filter!(s->s[1] in _names, arr)
213+
# create dict provided fields into a NamedTuple as a type stable "pre-struct"
214+
nt = (;arr...)
215+
# use keyword constructors provided by Base.@kwdef to resolve random ordering, incomplete dicts, and defaults
216+
T(;nt...)
217+
end
218+
219+
120220

121221
##==============================================================================
122222
## Variable Packing and unpacking
@@ -128,7 +228,7 @@ function packVariable(v::DFGVariable)
128228
props["nstime"] = string(v.nstime.value)
129229
props["tags"] = v.tags # JSON2.write(v.tags)
130230
props["ppeDict"] = v.ppeDict # JSON2.write(v.ppeDict)
131-
props["solverDataDict"] = (Dict(keys(v.solverDataDict) .=> map(vnd -> packVariableNodeData(dfg, vnd), values(v.solverDataDict)))) # JSON2.write
231+
props["solverDataDict"] = (Dict(keys(v.solverDataDict) .=> map(vnd -> packVariableNodeData(vnd), values(v.solverDataDict)))) # JSON2.write
132232
props["smallData"] = v.smallData # JSON2.write(v.smallData)
133233
props["solvable"] = v.solvable
134234
props["variableType"] = typeModuleName(getVariableType(v))

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ using UUIDs
2121
# logger = SimpleLogger(stdout, Logging.Debug)
2222
# global_logger(logger)
2323

24+
include("testTranscodeTypeUnmarshaling.jl")
2425
include("test_defVariable.jl")
2526

2627
include("testBlocks.jl")

test/testTranscodeTypeUnmarshaling.jl

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
2+
# test transcoding and unmarshal util
3+
4+
5+
using Test
6+
using DistributedFactorGraphs
7+
using DataStructures: OrderedDict
8+
using Dates
9+
10+
##
11+
12+
13+
Base.@kwdef struct HardType
14+
name::String
15+
time::DateTime = now(UTC)
16+
val::Float64 = 0.0
17+
end
18+
# # slight human overhead for each type to ignore extraneous field construction
19+
# # TODO, devnote drop this requirement with filter of _names in transcodeType
20+
# HardType(;
21+
# name::String,
22+
# time::DateTime = now(UTC),
23+
# val::Float64 = 0.0,
24+
# ignorekws...
25+
# ) = HardType(name,time,val)
26+
27+
@testset "Test transcoding of Intermediate, Dict, OrderedDict to a HardType" begin
28+
29+
struct IntermediateType
30+
_version
31+
_type
32+
name
33+
time
34+
val
35+
end
36+
37+
# somehow one gets an intermediate type
38+
imt = IntermediateType(
39+
v"1.0",
40+
"NotUsedYet",
41+
"test",
42+
now(UTC),
43+
1.0
44+
)
45+
# or dict (testing string keys)
46+
imd = Dict(
47+
"_version" => v"1.0",
48+
"_type" => "NotUsedYet",
49+
"name" => "test",
50+
"time" => now(UTC),
51+
"val" => 1.0
52+
)
53+
# ordered dict (testing symbol keys)
54+
iod = OrderedDict(
55+
:_version => v"1.0",
56+
:_type => "NotUsedYet",
57+
:name => "test",
58+
:time => now(UTC),
59+
# :val => 1.0
60+
)
61+
62+
# do the transcoding to a slighly different hard type
63+
T1 = DistributedFactorGraphs.transcodeType(HardType, imt)
64+
T2 = DistributedFactorGraphs.transcodeType(HardType, imd)
65+
T3 = DistributedFactorGraphs.transcodeType(HardType, iod)
66+
67+
end
68+
69+
Base.@kwdef struct MyType{T <: Real}
70+
tags::Vector{Symbol} = Symbol[]
71+
count::Int
72+
funfun::Complex{T} = 1 + 5im
73+
somedata::Dict{Symbol,Any} = Dict{Symbol, Any}()
74+
data::Vector{Float64} = zeros(0)
75+
binary::Vector{UInt8} = Vector{UInt8}()
76+
end
77+
78+
@testset "More super unmarshaling tests of various test dicts" begin
79+
80+
d = Dict("count" => 3)
81+
DistributedFactorGraphs.transcodeType(MyType, d)
82+
83+
d2 = Dict("count" => 3, "tags" => Any["hi", "okay"])
84+
DistributedFactorGraphs.transcodeType(MyType, d2)
85+
86+
d3 = Dict("count" => "3", "tags" => String["hi", "okay"])
87+
DistributedFactorGraphs.transcodeType(MyType, d3)
88+
89+
d4 = Dict("count" => 3.0, "funfun" => "8 - 3im", "tags" => Any["hi", "okay"])
90+
DistributedFactorGraphs.transcodeType(MyType{Float32}, d4)
91+
92+
d5 = Dict("count" => 3, "somedata" => Dict{String,Any}("calibration"=>[1.1;2.2], "description"=>"this is a test"))
93+
DistributedFactorGraphs.transcodeType(MyType{Float64}, d5)
94+
95+
d6 = Dict("count" => 3.0, "data" => Any[10, 60])
96+
DistributedFactorGraphs.transcodeType(MyType, d6)
97+
98+
d7 = Dict("count" => 3.0, "data" => String["10", "60"])
99+
DistributedFactorGraphs.transcodeType(MyType, d7)
100+
101+
d8 = Dict("count" => 4, "binary" => take!(IOBuffer("hello world")))
102+
DistributedFactorGraphs.transcodeType(MyType, d8)
103+
104+
d9 = Dict("count" => 4, "somedata" => Dict{Symbol,Any}(:test => "no ambiguity"))
105+
DistributedFactorGraphs.transcodeType(MyType, d9)
106+
107+
end

0 commit comments

Comments
 (0)