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

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
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
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
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
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
New

Other Trending Topics Top

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
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
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
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
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
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews