csaintc
Dealing with deeply nested structs
Hey friends,
I’m currently writing an integration to an API, and I’m curious of how to deal with deeply nested structs.
I have been writing a bunch of GoLang recently, and I love how all of your data structures end up being extremely well documented because everything is a struct.
I was trying to adopt a similar strategy for my API: Everything being documented and contained in a struct. But! I’m having issues. I end up having to write code like this in all of my modules:
def to_struct(body = %{"courses" => courses}) do
%__MODULE__{
courses: Enum.map(courses, fn(course) -> TheApi.Resources.TheStruct.to_struct(course) end)
}
end
Having to write boilerplate for constructing complex structs has not been particularly pleasent, and I can’t help but think I’m missing something obvious that would solve my problems.
Thanks a bunch!
Most Liked Responses
michalmuskala
This sounds like a perfect use case for a data conformance library, such as saul | Hex
IvanR
One possible alternative for the nested struct coercion with the following validation of conformance to struct’s @type t() and associated preconditions is with the Jason + Nestru + Domo libraries combo.
Usage example: GitHub - IvanRublev/elixir-decode-validate-json-with-nestru-domo · GitHub
Eiji
@csaintc: Here is how you can do it without any extra library:
# example structs:
defmodule Author do
defstruct contact_email: "", first_name: "", last_name: ""
end
defmodule Comment do
defstruct author: %Author{}, body: ""
end
defmodule Post do
defstruct author: %Author{}, body: "", comments: [], title: ""
end
# here is my module with to_struct proposition
defmodule Example do
# here we are calling to_struct for has_many associations
def to_struct(list, mod) when is_list(list) and is_atom(mod),
do: Enum.map(list, &to_struct(&1, mod))
# here we are calling to_struct for has_one and belongs_to association
def to_struct(map, key, mod) when is_map(map) and is_atom(key) and is_atom(mod),
do: Map.update!(map, key, &to_struct(&1, mod))
# here is custome to_struct for Post, because it has 2 extra relations
def to_struct(map, Post) do
Post
|> struct(map)
|> to_struct(:author, Author)
|> to_struct(:comments, Comment)
end
# here is custom to_struct for Comment, because it has 1 extra relation
def to_struct(map, Comment) do
Comment
|> struct(map)
|> to_struct(:author, Author)
end
# this applies to any other module - without relation i.e. sub-structs
def to_struct(map, module) when is_map(map) and is_atom(module),
do: struct(module, map)
end
# example post data:
post = %{
author: %{
contact_email: "michael_smith@example.com",
first_name: "Michael",
last_name: "Smith",
},
body: "Elixir rules! Elixir is the best! Elixir ...",
comments: [
%{
author: %{
contact_email: "david_smith@example.com",
first_name: "David",
last_name: "Smith",
},
body: "Nice article",
},
%{
author: %{
contact_email: "john_smith@example.com",
first_name: "John",
last_name: "Smith",
},
body: "John was here!",
}
],
title: "An awesome title here ...",
}
IO.inspect post
IO.inspect Example.to_struct(post, Post)
# you can also call lists of posts:
Example.to_struct([post], Post)
If I would suggest a library then it would be Ecto, because it also supports validations.








