csaintc

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!

First 9 of 9 Posts Switch mode

OvermindDL1

OvermindDL1

There are libraries out for that, one of the ones I like is ExConstructor. You still have to write the constructor with it, but it is nice. :slight_smile:

You can always write a macro for your specific case though to auto-generate struct mapping code too.

Eiji

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.

michalmuskala

michalmuskala

This sounds like a perfect use case for a data conformance library, such as saul | Hex

csaintc

csaintc OP

The solution I’ve come to is using the nested struct coersion that is built into Poison.

It looks like this:

def foo_bar(foo_id, bar_id) do
    foo_id
    |> TheApi.Resources.Bar.url(bar_id)
    |> Poison(%TheApi.Resources.Foo{as: bar_list: [%Bar{}]})
end

And it works exactly like I want it to.

Saul looks great. I’ll 100% be looking into it.

csaintc

csaintc OP

ExConstructor looks nice, but it does not currently support nested coersion, from what I can tell.

csaintc

csaintc OP

Quick question about Saul: Will it convert my maps to named structs?

I like structs because they are NAMED data structured, so my fellow developers know what properties to expect on these maps.

michalmuskala

michalmuskala

I don’t think there’s one built-in, but it’s rather easy to extend it.

OvermindDL1

OvermindDL1

Not by default, but fixable via a macro. :slight_smile:

IvanR

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

— All posts loaded —

Where Next?

Trending in Questions Top

jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
silverdr
Using Phoenix.LiveView.TagEngine as an EEx.Engine is deprecated! To compile HEEx, use Phoenix.LiveView.TagEngine.compile/2 instead. Sta...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
michallepicki
I am using Oban and occasionally, shortly after a deployment, a handful of jobs can fail because of dependency on other parts of the syst...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New

We're in Beta

About us Mission Statement