diff --git a/README.md b/README.md index c388b67..88c67af 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ dm = DotMap(dict) dm.c.d # returns 3 dm.c.e = 5 dm["c"].e # returns 5 + +DotMap.todict(dm, keys_as_strings=true) # returns Dict("a"=>1, "b"=>2, "c" => Dict("d"=>3, "e"=>5)) +DotMap.todict(dm) # returns Dict(:a=>1, :b=>2, :c => Dict(:d=>3, :e=>5)) ``` **NOTE** This is not as performative as using normal dictionaries, but is nice for accessing deeply nested dictionary structures, such as large config/yaml/json files. diff --git a/src/DotMaps.jl b/src/DotMaps.jl index 508624b..54ab060 100644 --- a/src/DotMaps.jl +++ b/src/DotMaps.jl @@ -3,7 +3,7 @@ module DotMaps export DotMap """ - DotMaps.DotMap(::Dict) + DotMaps.DotMap(::AbstractDict) Constructs a DotMap from a Dict. This provides the same functionaliity as dictionaries, but allows indexing with `.` instead of (or in addition to) `[""]`. """ @@ -12,7 +12,7 @@ mutable struct DotMap DotMap() = new(Dict{Symbol,Any}()) end -function DotMap(d::Dict) +function DotMap(d::AbstractDict) dm = DotMap() for (k, v) in d dm.__dict__[Symbol(k)] = DotMap(v) @@ -22,6 +22,24 @@ end DotMap(d::Any) = d +""" + DotMaps.todict(::DotMap; keys_as_strings=false) + +Constructs a Dict from a DotMap. If `keys_as_strings`, the keys will be `String` instead of `Symbol`. +""" +function todict(obj::DotMap; keys_as_strings::Bool = false) + dict = Dict() + for (k, v) in obj + nk = keys_as_strings ? string(k) : k + dict[nk] = todict(v, keys_as_strings = keys_as_strings) + end + + return dict +end + +# return at leaves +todict(obj::Any; keys_as_strings::Bool = false) = obj + # make dots work function Base.getproperty(obj::DotMap, name::Symbol) if name == :__dict__ @@ -31,16 +49,16 @@ function Base.getproperty(obj::DotMap, name::Symbol) end end -function Base.setproperty!(obj::DotMap, name::Symbol, x) - obj.__dict__[name] = x -end - # make dictionary indexing work Base.getindex(obj::DotMap, name::Symbol) = Base.getindex(obj.__dict__, name) Base.getindex(obj::DotMap, name::Any) = Base.getindex(obj, Symbol(name)) -Base.setindex!(obj::DotMap, name::Symbol, x) = Base.setindex!(obj.__dict__, name, x) -Base.setindex!(obj::DotMap, name, x) = Base.setindex!(obj, Symbol(name), x) +Base.setindex!(obj::DotMap, x, name::Symbol) = Base.setindex!(obj.__dict__, x, name) +Base.setindex!(obj::DotMap, x::Dict, name::Symbol) = Base.setindex!(obj.__dict__, DotMap(x), name) +Base.setindex!(obj::DotMap, x, name) = Base.setindex!(obj, x, Symbol(name)) + +# assignment with dots +Base.setproperty!(obj::DotMap, name::Symbol, x) = Base.setindex!(obj, x, name) # iteration Base.iterate(obj::DotMap) = Base.iterate(obj.__dict__) diff --git a/test/runtests.jl b/test/runtests.jl index ec6a5fb..4cc62a0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -19,6 +19,9 @@ using Test @test DM.c.e == 5 @test DM["c"].e == 5 + DM.c.f = Dict("g" => 6) + @test DM.c.f.g == 6 + for (k, v) in DM @test isa(k, Symbol) end @@ -39,4 +42,11 @@ using Test @test :a in propertynames(DM) @test isempty(DotMap()) + + d = DotMap() + d.a = Dict("b" => 2) + dd = DotMaps.todict(d, keys_as_strings=true) + @test dd == Dict("a" => Dict("b" => 2)) + dds = DotMaps.todict(d) + @test dds == Dict(:a => Dict(:b => 2)) end