# Parsing a Struct String - Code.eval\_string/3? Custom Parser? Other Options?

**URL:** <https://forum.elixirforum.com/t/parsing-a-struct-string-code-eval-string-3-custom-parser-other-options/51947>\
**Category:** Questions\
**Tags:** strings, parsers, code\
**Created:** [November 22, 2022, 9:39am UTC](https://forum.elixirforum.com/t/parsing-a-struct-string-code-eval-string-3-custom-parser-other-options/51947 "2022-11-22T09:39:01Z")\
**Posts on this page:** 1\
**Showing post:** 13

<div class="post-metadata">

**Author:** ![IvanR](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/ivanr/32/25095_2.png) [@IvanR](https://forum.elixirforum.com/u/IvanR)\
**Post date:** [November 27, 2022, 2:02am UTC](https://forum.elixirforum.com/t/parsing-a-struct-string-code-eval-string-3-custom-parser-other-options/51947/13 "2022-11-27T02:02:04Z")

</div>

Another more robust option is to use the structure of YAML or equivalent JSON that looks like the elixir definition above and utilize [Nestru](https://hexdocs.pm/nestru) library (bias warning - I’m the author) to turn the map into the nested structs like the following:

## Install dependencies

```elixir
Mix.install(
  [
    {:nestru, "~> 0.3.2"},
    {:yaml_elixir, "~> 2.9"}
  ],
  consolidate_protocols: false
)

```

## Define the payload

All nodes with childs has the same named `child_list` for simplicity.

```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
  """
  |> YamlElixir.read_from_string!()

```

```elixir
%{
  "Parent" => %{
    "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"}}
    ],
    "field" => "Value"
  }
}

```

## Define nodes

```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

```

## Implement Nestru.Decoder for nodes

`StructDetector.split_module_fields/1` returns the list of struct atom and fields from the given maps list (or a single map). `Nestru` will call it with an appropriate map to build the child list of nested structs according to the hint.

```elixir
defmodule StructDetector do
  def split_module_fields(map) do
    list = List.wrap(map)

    try do
      {:ok,
       Enum.flat_map(list, fn a_map ->
         Enum.map(a_map, fn {module_string, fields} ->
           {Module.safe_concat([module_string]), fields}
         end)
       end)}
    rescue
      ArgumentError -> {: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} <-
             StructDetector.split_module_fields(Map.fetch!(map, "child_list")) do
        {modules_list, fields_list} = Enum.unzip(module_fields_list)

        {:ok,
         %{
           child_list: fn _value -> Nestru.decode_from_list_of_maps(fields_list, modules_list) end
         }}
      end
    else
      # Empty hint, decode all keys as-is.
      {:ok, %{}}
    end
  end
end

```

## Decode

```elixir
{:ok, [{root_module, fields}]} = StructDetector.split_module_fields(payload)
Nestru.decode_from_map!(fields, root_module)

```

```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}
  ]
}

```

`payload` can start from any node that `Nestru.Decoder` protocol is implemented for and be of any level of nestedness.

And you can easily support new nodes by including their names in the list in `defimpl`.

---

_[View the full topic](https://forum.elixirforum.com/t/parsing-a-struct-string-code-eval-string-3-custom-parser-other-options/51947)._
