# Metaprogramming with Structure: Converting Dictionary to Structure

**URL:** <https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861>\
**Category:** Data\
**Tags:** metaprogramming, data, dictionary, struct, toml\
**Created:** [June 30, 2021, 9:21pm UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861 "2021-06-30T21:21:01Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![JosephPollacco](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josephpollacco/32/51404_2.png) [@JosephPollacco](https://discourse.julialang.org/u/JosephPollacco)\
**Post date:** [June 30, 2021, 9:21pm UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/1 "2021-06-30T21:21:02Z")

</div>

Dear Julian colleagues,

I am reading the _options_ of my model from TOML file into Structure. With the following steps:

- The TOML writes into a Dict:
- Than I am writing a code to parse **Dict** into **Structure**

> The code works. But the question is how can I remove the **if iKey == “evapotranspiration”** and **elseif iKey == “soil”.**  
> Putting the question in another way is how do I transform a _String_ into **Struct**. I was not successful in using _eval methods_.

Many thanks for helping to automatize the process 🤠  
Joseph

```julia
# DEFINING STRUCTURE
   mutable struct EVAPOTRANSPIRATION
      Evaporation::Bool
      Transpiration::Bool
   end

   mutable struct SOIL
      Topsoil :: Bool
      Macropore :: Bool
   end

   mutable struct OPTION
      evapotranspiration::EVAPOTRANSPIRATION
      soil::SOIL 
   end

using TOML, StructTypes
function TOML_TEST()
   # PARSING TOML FILE
      PathHome = @ __DIR__
      Path = PathHome * "/Toml.toml"
      # Dict{String, Any}("evapotranspiration" => Dict{String, Any}("Evaporation" => true, "Transpiration" => false), "soil" => Dict{String, Any}("Topsoil" => true, "Macropore" => false))
      TomlParse = TOML.tryparsefile(Path)

   # INITIAL VALUES OF STRUCTURE
      Evaporation::Bool =false
      Transpiration::Bool=false
      evapotranspiration=EVAPOTRANSPIRATION(Evaporation, Transpiration)
   
      Topsoil::Bool=false
      Macropore::Bool=false
      soil = SOIL(Topsoil, Macropore)

   # LOOPING THROUGH THE DICT
   for (iKey, iValue₀) in TomlParse

      for iValue in (keys(iValue₀))
         if iKey == "evapotranspiration"
            setfield!(evapotranspiration, Symbol(iValue), TomlParse[iKey][iValue])

         elseif iKey == "soil"
            setfield!(soil, Symbol(iValue), TomlParse[iKey][iValue])
         end 
      end
   end

   option = OPTION(evapotranspiration, soil)
   
 # OUTPUT IS AS EXPECTED
   println(option.evapotranspiration.Evaporation)
   println(option.evapotranspiration.Transpiration)
   println(option.soil.Topsoil)
   println(option.soil.Macropore)
end

```

```julia
# TOML INPUT FILE
[evapotranspiration]
	Evaporation = true
   Transpiration = false

[soil]
  Topsoil = true
	Macropore = false

```

---

<div class="post-metadata">

**Author:** ![stephenll](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stephenll/32/28751_2.png) [@stephenll](https://discourse.julialang.org/u/stephenll)\
**Post date:** [July 1, 2021, 2:29pm UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/2 "2021-07-01T14:29:49Z")

</div>

@JosephPollacco  
A few caveats… Not an expert in Julia. I’m very much a beginner. Also, I don’t know your end goal or the edge cases but here is an option without the loop at all:

```julia
TomlParse = TOML.tryparse(strs)

splat1 = NamedTuple{Tuple(Symbol.(keys(TomlParse["evapotranspiration"])))}(values(TomlParse["evapotranspiration"]))
splat2 = NamedTuple{Tuple(Symbol.(keys(TomlParse["soil"])))}(values(TomlParse["soil"]))

output = OPTION(EVAPOTRANSPIRATION(splat1...), SOIL(splat2...))

```

There may be another way without having to convert to a named tuple but nothing I tried worked so far.

If I think of anything else I will post.

---

<div class="post-metadata">

**Author:** ![JosephPollacco](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josephpollacco/32/51404_2.png) [@JosephPollacco](https://discourse.julialang.org/u/JosephPollacco)\
**Post date:** [July 1, 2021, 11:33pm UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/3 "2021-07-01T23:33:00Z")

</div>

Dear @stepenl you should be a genius 🤠 if you are a beginner. You solved my problem beautifully:

1. The Toml file does not need to be ordered as in the structure;
2. You do not need to predeclare the STRUCTURE e.g.

```julia
      Evaporation::Bool =false
      Transpiration::Bool=false
      evapotranspiration=EVAPOTRANSPIRATION(Evaporation, Transpiration)

```

**Below is the solution of your code**

```julia
# DEFINING STRUCTURE
   mutable struct EVAPOTRANSPIRATION
      Evaporation::Bool
      Transpiration::Bool
   end

   mutable struct SOIL
      Topsoil :: Bool
      Macropore :: Bool
   end

   mutable struct OPTION
      evapotranspiration::EVAPOTRANSPIRATION
      soil::SOIL 
   end

using TOML
function TOML_TEST()

   # PARSING TOML FILE
      Path= "D:\\Main\\MODELS\\SoilWater-ToolBox2\\src\\Temporary\\Toml.toml"
      TomlParse = TOML.parsefile(Path)

   # STRUCTURE
      evapotranspiration = TOML_2_STRUCT(EVAPOTRANSPIRATION, TomlParse; MyType_LowerCase=true)

      soil = TOML_2_STRUCT(SOIL,TomlParse; MyType_LowerCase=true)

   option = OPTION(evapotranspiration, soil)
   
   # TESTING
      println(option.evapotranspiration.Evaporation)
      println(option.evapotranspiration.Transpiration)
      println(option.soil.Topsoil)
      println(option.soil.Macropore)
end

   function TOML_2_STRUCT(Structure, TomlParse; MyType_LowerCase=true, MyType=:MyType)
      if MyType_LowerCase == false
         MyType = string(MyType)
      else
         MyType = lowercase.(string(Structure))
      end

      Output = NamedTuple{Tuple(Symbol.(keys(TomlParse[MyType])))}(values(TomlParse[MyType]))
     return Structure(Output...)
   end

```

---

<div class="post-metadata">

**Author:** ![SteffenPL](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/steffenpl/32/206270_2.png) [@SteffenPL](https://discourse.julialang.org/u/SteffenPL)\
**Post date:** [July 2, 2021, 12:08am UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/4 "2021-07-02T00:08:50Z")

</div>

I do something similar and what helped me was:

- You could use `Base.@kwdef` to give the structs default values directly.
- There is a package [Home · StructTypes.jl](https://juliadata.github.io/StructTypes.jl/stable/) which is well suited for the task of converting a `Dict` into `struct`s.

Anyway, since you have already a working solution, this might not be needed anymore 🙂

---

<div class="post-metadata">

**Author:** ![JosephPollacco](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josephpollacco/32/51404_2.png) [@JosephPollacco](https://discourse.julialang.org/u/JosephPollacco)\
**Post date:** [July 2, 2021, 4:15am UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/5 "2021-07-02T04:15:25Z")

</div>

Hi Stephen,

It will be informative if you could give an example how to use [Home · StructTypes.jl](https://juliadata.github.io/StructTypes.jl/stable/) as the documentation is a bit overwhelming for simple users.

---

<div class="post-metadata">

**Author:** ![jzr](https://avatars.discourse-cdn.com/v4/letter/j/eb9ed0/32.png) [@jzr](https://discourse.julialang.org/u/jzr)\
**Post date:** [July 2, 2021, 4:41am UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/6 "2021-07-02T04:41:44Z")

</div>

I think this section might be most helpful

[https://juliadata.github.io/StructTypes.jl/stable/#StructTypes.AbstractType](https://juliadata.github.io/StructTypes.jl/stable/#StructTypes.AbstractType)

---

<div class="post-metadata">

**Author:** ![Skoffer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/skoffer/32/378_2.png) [@Skoffer](https://discourse.julialang.org/u/Skoffer)\
**Post date:** [July 2, 2021, 4:54am UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/7 "2021-07-02T04:54:48Z")

</div>

Not an answer to your question, but have you seen [[ANN] Configurations.jl - Options/Configurations made easy](https://discourse.julialang.org/t/ann-configurations-jl-options-configurations-made-easy/50669)? May be there is no need to write your own implementation.

---

<div class="post-metadata">

**Author:** ![Roger-luo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roger-luo/32/3399_2.png) [@Roger-luo](https://discourse.julialang.org/u/Roger-luo)\
**Post date:** [July 2, 2021, 8:23pm UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/8 "2021-07-02T20:23:01Z")

</div>

Yeah basically just replace the kwdef macro with option in Configuration then it should just work.

---

<div class="post-metadata">

**Author:** ![jessymilare](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jessymilare/32/13750_2.png) [@jessymilare](https://discourse.julialang.org/u/jessymilare)\
**Post date:** [July 2, 2021, 8:47pm UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/9 "2021-07-02T20:47:01Z")

</div>

@JosephPollacco A warning, that code it’s not doing what you think it is doing.

In the the call `EVAPOTRANSPIRATION(splat1...)`, the values of the named tuple are given to `EVAPOTRANSPIRATION` in the order of the construction of the named tuple (the keys are simply ignored). In other words, if the keys are reversed in the TOML file, the values of the struct will be reversed, too.

Using `Base.@kwdef` and `(; kwargs...)` should solve that issue, and `kwargs` can be a `NamedTuple` or any dictionary with `Symbol` keys. The semicolon is needed for Julia to interpret the keys and values in `kwargs` as keyword arguments.

```julia
@kwdef mutable struct EVAPOTRANSPIRATION
    Evaporation::Bool
    Transpiration::Bool
end

@kwdef mutable struct SOIL
    Topsoil :: Bool
    Macropore :: Bool
end

@kwdef mutable struct OPTION
    evapotranspiration::EVAPOTRANSPIRATION
    soil::SOIL 
end

using TOML

TomlParse = TOML.tryparse(strs)

splat1 = NamedTuple{Tuple(Symbol.(keys(TomlParse["evapotranspiration"])))}(values(TomlParse["evapotranspiration"]))
splat2 = NamedTuple{Tuple(Symbol.(keys(TomlParse["soil"])))}(values(TomlParse["soil"]))

evapotrans = EVAPOTRANSPIRATION(; splat1...)
soil = SOIL(; splat2...)

output = OPTION(evapotrans, soil)

```

---

<div class="post-metadata">

**Author:** ![stephenll](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stephenll/32/28751_2.png) [@stephenll](https://discourse.julialang.org/u/stephenll)\
**Post date:** [July 2, 2021, 9:52pm UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/10 "2021-07-02T21:52:32Z")

</div>

Thanks for the correction. When I use code like this I have a constructor for the struct using keyword arguments. I forgot to add that to the example code. I’ve never used the @kwef macro before.

---

<div class="post-metadata">

**Author:** ![JosephPollacco](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josephpollacco/32/51404_2.png) [@JosephPollacco](https://discourse.julialang.org/u/JosephPollacco)\
**Post date:** [July 5, 2021, 10:26pm UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/11 "2021-07-05T22:26:47Z")

</div>

Thanks @jessymilare for your response to improve the robustness of the code. I am not sure if I understood the advantages of using **Base.@kwdef** as in the code below seems to work independently of the order of TOML input:

**OPTION 1**

```julia
[evapotranspiration]
   Transpiration = 2
	Evaporation = 1
[soil]
	Macropore = false
  Topsoil = true

```

**OPTION 2**

```julia
[evapotranspiration]
   Evaporation = 1
   Transpiration = 2

[soil]
	Macropore = false
  Topsoil = true

```

**The code I am using is:**

````julia
   mutable struct EVAPOTRANSPIRATION
      Evaporation
      Transpiration
   end
mutable struct SOIL
      Topsoil :: Bool
      Macropore :: Bool
   end
mutable struct OPTION
      evapotranspiration::EVAPOTRANSPIRATION
      soil::SOIL 
   end

using TOML
function TOML_TEST()
   # PARSING TOML FILE
      Path= "D:\\Main\\MODELS\\SoilWater-ToolBox2\\src\\Temporary\\Toml.toml"
      TomlParse = TOML.parsefile(Path)

   # STRUCTURE
      evapotranspiration = TOML_2_STRUCT(EVAPOTRANSPIRATION, TomlParse; MyType_LowerCase=true)

     soil = TOML_2_STRUCT(SOIL,TomlParse; MyType_LowerCase=true)

   option = OPTION(evapotranspiration, soil)
   
   # TESTING
      println(option.evapotranspiration.Evaporation)
      println(option.evapotranspiration.Transpiration)
      println(option.soil.Topsoil)
      println(option.soil.Macropore)
end

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# FUNCTION : TOML_2_STRUCT
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   function TOML_2_STRUCT2(Structure, TomlParse)
      # LOOPING THROUGH THE DICT
      for (iKey, iValue₀) in TomlParse
      for iValue in (keys(iValue₀))
         if uppercase.(iKey) == (string(typeof(Structure)))
            setfield!(Structure, Symbol(iValue), TomlParse[iKey][iValue])
         end 
      end
   end
   return Structure
   end # function: TOML_2_STRUCT

   function TOML_2_STRUCT(Structure, TomlParse; MyType_LowerCase=true, MyType=:MyType)
      if MyType_LowerCase == false
         MyType = string(MyType)
      else
         MyType = lowercase.(string(Structure))
      end

      Output = NamedTuple{Tuple(Symbol.(keys(TomlParse[MyType])))}(values(TomlParse[MyType]))
     return Structure(Output...)
   end```
````

---

<div class="post-metadata">

**Author:** ![JosephPollacco](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josephpollacco/32/51404_2.png) [@JosephPollacco](https://discourse.julialang.org/u/JosephPollacco)\
**Post date:** [July 5, 2021, 11:18pm UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/12 "2021-07-05T23:18:46Z")

</div>

The solution by @Skoffer works remarkably well by using **Configurations** [Configuration.jl](https://github.com/Roger-luo/Configurations.jl)and is the solution I was looking for as it can read complex TOML structure file automatically.

**My recommendation is that Configuration.jl should be part of the TOML package.**

```julia
using Configurations
@option mutable struct EVAPOTRANSPIRATION
      Evaporation
      Transpiration
   end

   @option mutable struct SOIL
      Topsoil 
      Macropore 
   end

   @option mutable struct OPTION
      evapotranspiration::EVAPOTRANSPIRATION
      soil::SOIL 
   end

function TOML_TEST()
   # PARSING TOML FILE
      Path= "D:\\Main\\MODELS\\SoilWater-ToolBox2\\src\\Temporary\\Toml.toml"
      # TomlParse = TOML.parsefile(Path)

      option = Configurations.from_toml(OPTION, Path)
  
   # TESTING
      println(option.evapotranspiration.Evaporation)
      println(option.evapotranspiration.Transpiration)
      println(option.soil.Macropore)
      println(option.soil.Topsoil)
end

```

---

<div class="post-metadata">

**Author:** ![jessymilare](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jessymilare/32/13750_2.png) [@jessymilare](https://discourse.julialang.org/u/jessymilare)\
**Post date:** [July 7, 2021, 11:35pm UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/13 "2021-07-07T23:35:01Z")

</div>

The problem I refered to still exists with you function `TOML_2_STRUCT`. It seems to be working by coincidence (`TOML` creates a `Dict`, which sorts by hash).

Let me try to illustrate the issue.

```julia
julia> mutable struct EVAPOTRANSPIRATION
           Evaporation
           Transpiration
       end

julia> EVAPOTRANSPIRATION(true, false)
EVAPOTRANSPIRATION(true, false)

julia> EVAPOTRANSPIRATION(false, true)
EVAPOTRANSPIRATION(false, true)

julia> EVAPOTRANSPIRATION(Evaporation = true, Transpiration = false)
ERROR: MethodError: no method matching EVAPOTRANSPIRATION(; Evaporation=true, Transpiration=false)
Closest candidates are:
  EVAPOTRANSPIRATION(::Any, ::Any) at REPL[1]:2 got unsupported keyword arguments "Evaporation", "Transpiration"
Stacktrace:
 [1] top-level scope
   @ REPL[4]:1

```

See? The constructor `EVAPOTRANSPIRATION` accepts **positional** arguments, not **keyword** arguments. (Keyword arguments are arguments with names).

This is what is what might eventually happen inside your `TOML_2_STRUCT`:

```julia
julia> Output = (Transpiration = false, Evaporation = true)
(Transpiration = false, Evaporation = true)

julia> EVAPOTRANSPIRATION(Output...)
EVAPOTRANSPIRATION(false, true)

julia> ans.Evaporation
false

```

The variable `Output` contains a `NamedTuple` with `:Evaporation` key set to true, but the constructed `EVAPOTRANSPIRATION` has the first field (which is `Evaporation`) set to false.

What is happening is this. The call `EVAPOTRANSPIRATION(Output...)` is evaluated as `EVAPOTRANSPIRATION(false, true)` because the **values** of the named tuple are spliced as **positional** arguments and the key names are ignored. So, the `false` (which refers to `:Transpiration`) is supplied as first argument for `EVAPOTRANSPIRATION`, which refers to `Evaporation` field.

To resolve that, you need to use `Base.@kwdef`, which creates a constructor that accepts keyword arguments, **AND** to change `EVAPOTRANSPIRATION(Output...)` into `EVAPOTRANSPIRATION(; Output...)` that is, with a semicolon before `Output` so that it is spliced as keyword arguments instead of positional arguments.

---

<div class="post-metadata">

**Author:** ![jessymilare](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jessymilare/32/13750_2.png) [@jessymilare](https://discourse.julialang.org/u/jessymilare)\
**Post date:** [July 7, 2021, 11:44pm UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/14 "2021-07-07T23:44:05Z")

</div>

Another example with keyword arguments:

```julia
julia> function foo(; x = 0, y = 0)
           x + y
       end
foo (generic function with 1 method)

julia> foo(x = 1, y = 2)
3

julia> kwargs = (x = 10, y = 20)
(x = 10, y = 20)

julia> foo(kwargs...)
ERROR: MethodError: no method matching foo(::Int64, ::Int64)
Stacktrace:
 [1] top-level scope
   @ REPL[21]:1

julia> foo(; kwargs...)
30

```

`foo` accepts no positional argument and two keyword arguments. `kwargs` is a named tupe and `foo(kwargs...)` doesn’t work because the arguments are supplied as positional arguments. But `foo(; kwargs...)` works because the semicolon tells Julia that the named tuple `kwargs` must be spliced as keyword arguments.

---

<div class="post-metadata">

**Author:** ![JosephPollacco](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josephpollacco/32/51404_2.png) [@JosephPollacco](https://discourse.julialang.org/u/JosephPollacco)\
**Post date:** [July 11, 2021, 10:40pm UTC](https://discourse.julialang.org/t/metaprogramming-with-structure-converting-dictionary-to-structure/63861/15 "2021-07-11T22:40:46Z")

</div>

@jessymilare a great thanks for providing the community such great explanations which explains beautifully about positional arguments.
