csaintc

csaintc

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!

Showing Posts 1 to 9

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? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
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
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews