Run this notebook

Use Livebook to open this notebook and explore new ideas.

It is easy to get started, on your machine or the cloud.

Click below to open and run it in your Livebook at .

(or change your Livebook location)

# Nestru example for Lisbon Elixir meetup at 23 Nov 2022 ```elixir Mix.install( [ {:nestru, "~> 0.3.2"}, {:jason, "~> 1.3"}, {:ex_json_schema, "~> 0.9.2"}, {:ecto_sql, "~> 3.7"}, {:postgrex, "~> 0.16.1"}, {:exjsonpath, "~> 0.9.0"} ], consolidate_protocols: false ) defmodule Repo do use Ecto.Repo, adapter: Ecto.Adapters.Postgres, otp_app: :my_app end Application.put_env(:my_app, Repo, username: "postgres", password: "postgres", # Please, create the database if it does not exists database: "nestru_example", hostname: "localhost", port: 5432, pool_size: 5 ) pid = Repo.start_link() Repo.query!(""" CREATE TABLE IF NOT EXISTS squads ( id SERIAL PRIMARY KEY, squad_name text, formed integer, secret_base text, active boolean ) """) Repo.query!("CREATE UNIQUE INDEX IF NOT EXISTS squads_pkey ON squads(id int4_ops)") Repo.query!(""" CREATE TABLE IF NOT EXISTS super_heroes ( id SERIAL PRIMARY KEY, name text, age integer, secret_identity text, powers text[], squad_id integer REFERENCES squads(id) ON DELETE CASCADE ) """) Repo.query!("CREATE UNIQUE INDEX IF NOT EXISTS super_heroes_pkey ON super_heroes(id int4_ops)") pid ``` ## Library <p align="center" class="hidden"> <a href="https://livebook.dev/run?url=https%3A%2F%2Fgist.github.com%2FIvanRublev%2Fd6c77c64f7c94976828d606c5fcfe6d8"> <img src="https://livebook.dev/badge/v1/blue.svg" alt="Run in Livebook" /> </a> </p> https://github.com/IvanRublev/Nestru ## Input JSON ```elixir payload = """ { "squad_name": "Super hero squad", "home_town": "Metro City", "formed": 2016, "secret_base": "Super tower", "active": true, "members": [ { "name": "Molecule Man", "age": 29, "secretIdentity": "Dan Jukes", "powers": [ "Radiation resistance", "Turning tiny", "Radiation blast" ] }, { "name": "Madame Uppercut", "age": 39, "secretIdentity": "Jane Wilson", "powers": [ "Million tonne punch", "Damage resistance", "Superhuman reflexes" ] }, { "name": "Eternal Flame", "age": 1000000, "secretIdentity": "Unknown", "powers": [ "Immortality", "Heat Immunity", "Inferno", "Teleportation", "Interdimensional travel" ] } ] } """ |> Jason.decode!() ``` ```elixir ~s({"$schema":"http://json-schema.org/draft-04/schema#","type":"object","properties":{"squad_name":{"type":"string"},"home_town":{"type":"string"},"formed":{"type":"integer"},"secret_base":{"type":"string"},"active":{"type":"boolean"},"members":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"},"secretIdentity":{"type":"string"},"powers":{"type":"array","items":{"type":"string"}}},"required":["name","age","secretIdentity","powers"]}}},"required":["squad_name","home_town","formed","secret_base","active","members"]}) |> Jason.decode!() |> ExJsonSchema.Schema.resolve() |> ExJsonSchema.Validator.validate(payload) ``` ```elixir payload_beta = """ { "heroes": [ { "name": "Rorschach", "age": 35, "secretIdentity": "Walter Joseph Kovacs", "powers": ["curiosity"] } ] } """ |> Jason.decode!() ``` ```elixir ~s({"$schema":"http://json-schema.org/draft-04/schema#","type":"object","properties":{"heroes":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"},"secretIdentity":{"type":"string"},"powers":{"type":"array","items":{"type":"string"}}},"required":["name","age","secretIdentity","powers"]}}},"required":["heroes"]}) |> Jason.decode!() |> ExJsonSchema.Schema.resolve() |> ExJsonSchema.Validator.validate(payload_beta) ``` ```elixir payload_alpha = """ { "a":{ "very":{ "nested":{ "heroes":[ { "name":"Rorschach", "age":35, "secretIdentity":"Walter Joseph Kovacs", "powers":[ "curiosity" ] } ] } } } } """ |> Jason.decode!() ``` ## Application Model ```elixir import Ecto.Changeset defmodule Squad do use Ecto.Schema schema "squads" do field(:squad_name, :string) field(:formed, :integer) field(:secret_base, :string) field(:active, :boolean) has_many(:members, SuperHero) end # def changeset(changeset \\ %__MODULE__{}, params) do # keys = __schema__(:fields) -- __schema__(:primary_key) # changeset # |> cast(params, keys) # |> validate_required(keys) # |> cast_assoc(:members) # end end defmodule SuperHero do use Ecto.Schema schema "super_heroes" do field(:name, :string) field(:age, :integer) field(:secret_identity, :string) field(:powers, {:array, :string}) belongs_to(:squad, Squad) end # def changeset(changeset \\ %__MODULE__{}, params) do # keys = __schema__(:fields) -- ([:squad, :squad_id] ++ __schema__(:primary_key)) # changeset # |> cast(params, keys) # |> validate_required(keys) # |> validate_length(:powers, min: 1) # end end ``` ```elixir require Protocol defimpl Nestru.PreDecoder, for: Squad do def gather_fields_from_map(_value, :alpha, map) do with {:ok, members} <- ExJSONPath.eval(map, "$.a.very.nested.heroes.*") do {:ok, %{ squad_name: "A squad (from alpha)", formed: 2000, secret_base: "Unknown", active: true, members: members }} end end def gather_fields_from_map(_value, :beta, map) do {:ok, %{ squad_name: "A squad (from beta)", formed: 2000, secret_base: "Unknown", active: true, members: Map.get(map, "heroes") }} end def gather_fields_from_map(_value, _context, map) do {:ok, map} end end Protocol.derive(Nestru.Decoder, Squad, hint: %{members: [SuperHero]}) Protocol.derive(Nestru.PreDecoder, SuperHero, translate: %{"secretIdentity" => :secret_identity}) Protocol.derive(Nestru.Decoder, SuperHero) Protocol.derive(Nestru.Encoder, Squad, except: [:__meta__]) Protocol.derive(Nestru.Encoder, SuperHero, except: [:__meta__, :squad, :squad_id]) ``` ```elixir squad = Nestru.decode_from_map!(payload, Squad) ``` ```elixir squad_beta = Nestru.decode_from_map!(payload_beta, Squad, :beta) ``` ```elixir squad_alpha = Nestru.decode_from_map!(payload_alpha, Squad, :alpha) ``` ```elixir Repo.insert!(squad) Repo.insert!(squad_beta) ``` ```elixir squads = Squad |> Repo.all() |> Repo.preload(:members) ``` ```elixir map = Nestru.encode_to_list_of_maps!(squads) ``` ```elixir map |> Jason.encode!() |> IO.puts() ``` ## Links * Visualize JSON with https://jsoncrack.com/editor * JSON to JSON Schema converter https://www.liquid-technologies.com/online-json-to-schema-converter * JSON Minifier https://codebeautify.org/jsonminifier * Livebooks collection https://notes.club/
See source

Have you already installed Livebook?

If you already installed Livebook, you can configure the default Livebook location where you want to open notebooks.
Livebook up Checking status We can't reach this Livebook (but we saved your preference anyway)
Run notebook

Not yet? Install Livebook in just a minute

Livebook is open source, free, and ready to run anywhere.

Run on your machine

with Livebook Desktop

Run in the cloud

on select platforms

To run on Linux, Docker, embedded devices, or Elixir’s Mix, check our README.

PLATINUM SPONSORS
SPONSORS
Code navigation with go to definition of modules and functions Read More ×