<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="269338" data-post-id="269338">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="hst337" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  hst337
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Don’t forget to mark the solution</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="269338" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/parsing-a-struct-string-code-eval-string-3-custom-parser-other-options/51947/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-269338" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="269338"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #11"></div>
  </section>
</div>
    <div class="postbit" id="269521" data-post-id="269521">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="IvanR" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/IvanR/120/25095_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  IvanR
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Another more robust option is to use the structure of YAML or equivalent JSON that looks like the elixir definition above and utilize <a href="https://hexdocs.pm/nestru" rel="noopener nofollow ugc">Nestru</a> library (bias warning - I’m the author) to turn the map into the nested structs like the following:</p>
<h2><a name="p-269521-install-dependencies-1" class="anchor" href="#p-269521-install-dependencies-1" aria-label="Heading link" rel="nofollow"></a>Install dependencies</h2>
<pre data-code-wrap="elixir"><code class="lang-elixir">Mix.install(
  [
    {:nestru, "~&gt; 0.3.2"},
    {:yaml_elixir, "~&gt; 2.9"}
  ],
  consolidate_protocols: false
)
</code></pre>
<h2><a name="p-269521-define-the-payload-2" class="anchor" href="#p-269521-define-the-payload-2" aria-label="Heading link" rel="nofollow"></a>Define the payload</h2>
<p>All nodes with childs has the same named <code>child_list</code> for simplicity.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">payload =
  """
  Parent:
    field: Value
    another_field: "Another value"
    child_list:
      - Child:
          child_field: Child 1 data
          child_list:
            - TypeOne:
                field: Value
            - TypeTwo:
                another_field: Some other value
      - Child:
          child_field: Child 2 data
  """
  |&gt; YamlElixir.read_from_string!()
</code></pre>

<pre data-code-wrap="elixir"><code class="lang-elixir">%{
  "Parent" =&gt; %{
    "another_field" =&gt; "Another value",
    "child_list" =&gt; [
      %{
        "Child" =&gt; %{
          "child_field" =&gt; "Child 1 data",
          "child_list" =&gt; [
            %{"TypeOne" =&gt; %{"field" =&gt; "Value"}},
            %{"TypeTwo" =&gt; %{"another_field" =&gt; "Some other value"}}
          ]
        }
      },
      %{"Child" =&gt; %{"child_field" =&gt; "Child 2 data"}}
    ],
    "field" =&gt; "Value"
  }
}
</code></pre>
<h2><a name="p-269521-define-nodes-3" class="anchor" href="#p-269521-define-nodes-3" aria-label="Heading link" rel="nofollow"></a>Define nodes</h2>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Parent do
  defstruct [:field, :another_field, :child_list]
end

defmodule Child do
  defstruct [:child_field, :child_list]
end

defmodule TypeOne do
  defstruct [:field]
end

defmodule TypeTwo do
  defstruct [:another_field]
end
</code></pre>
<h2><a name="p-269521-implement-nestrudecoder-for-nodes-4" class="anchor" href="#p-269521-implement-nestrudecoder-for-nodes-4" aria-label="Heading link" rel="nofollow"></a>Implement Nestru.Decoder for nodes</h2>
<p><code>StructDetector.split_module_fields/1</code> returns the list of struct atom and fields from the given maps list (or a single map). <code>Nestru</code> will call it with an appropriate map to build the child list of nested structs according to the hint.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule StructDetector do
  def split_module_fields(map) do
    list = List.wrap(map)

    try do
      {:ok,
       Enum.flat_map(list, fn a_map -&gt;
         Enum.map(a_map, fn {module_string, fields} -&gt;
           {Module.safe_concat([module_string]), fields}
         end)
       end)}
    rescue
      ArgumentError -&gt; {:error, :nonexisting_struct}
    end
  end
end

require Protocol

defimpl Nestru.Decoder, for: [Parent, Child, TypeOne, TypeTwo] do
  def from_map_hint(_struct, _context, map) do
    if Map.has_key?(map, "child_list") do
      with {:ok, module_fields_list} &lt;-
             StructDetector.split_module_fields(Map.fetch!(map, "child_list")) do
        {modules_list, fields_list} = Enum.unzip(module_fields_list)

        {:ok,
         %{
           child_list: fn _value -&gt; Nestru.decode_from_list_of_maps(fields_list, modules_list) end
         }}
      end
    else
      # Empty hint, decode all keys as-is.
      {:ok, %{}}
    end
  end
end
</code></pre>
<h2><a name="p-269521-decode-5" class="anchor" href="#p-269521-decode-5" aria-label="Heading link" rel="nofollow"></a>Decode</h2>
<pre data-code-wrap="elixir"><code class="lang-elixir">{:ok, [{root_module, fields}]} = StructDetector.split_module_fields(payload)
Nestru.decode_from_map!(fields, root_module)
</code></pre>

<pre data-code-wrap="elixir"><code class="lang-elixir">%Parent{
  field: "Value",
  another_field: "Another value",
  child_list: [
    %Child{
      child_field: "Child 1 data",
      child_list: [%TypeOne{field: "Value"}, %TypeTwo{another_field: "Some other value"}]
    },
    %Child{child_field: "Child 2 data", child_list: nil}
  ]
}
</code></pre>
<p><code>payload</code> can start from any node that <code>Nestru.Decoder</code> protocol is implemented for and be of any level of nestedness.</p>
<p>And you can easily support new nodes by including their names in the list in <code>defimpl</code>.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="269521" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/parsing-a-struct-string-code-eval-string-3-custom-parser-other-options/51947/13">Post #12</a>
	                </div>
	            </div>
              <div id="likers-container-269521" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="269521"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #12"></div>
  </section>
</div>
    <div class="postbit" id="269562" data-post-id="269562">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="dorgan" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/dorgan/120/22107_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  dorgan
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Keep in mind that <code>Code.string_to_quoted</code> is as unsafe as <code>String.to_atom</code>. A new atom will be created for each key, identifier, etc in your input, so it’s an invitation to denial of service. You can avoid this by using <a href="https://hexdocs.pm/elixir/Code.html#string_to_quoted/2-the-static_atoms_encoder-function" rel="noopener nofollow ugc"><code>:static_atoms_encoder</code></a> but never, ever parse user input without taking that into account.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="269562" data-batch-url="/posts/batch_likers">
                        4
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/parsing-a-struct-string-code-eval-string-3-custom-parser-other-options/51947/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-269562" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="269562"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #13"></div>
  </section>
</div>
    <div class="postbit" id="270352" data-post-id="270352">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="kingdomcoder" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/kingdomcoder/120/24667_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  kingdomcoder
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Whoa!</p>
<p>Thanks a ton. I didn’t consider this.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="270352" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/parsing-a-struct-string-code-eval-string-3-custom-parser-other-options/51947/15">Post #14</a>
	                </div>
	            </div>
              <div id="likers-container-270352" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="270352"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-last-post cat-last-post" title="Last post!"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <span class="all-loaded">— All posts loaded —</span>
</div></template></turbo-stream>