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
https://github.com/IvanRublev/Nestru
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!()
~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)
payload_beta =
"""
{
"heroes": [
{
"name": "Rorschach",
"age": 35,
"secretIdentity": "Walter Joseph Kovacs",
"powers": ["curiosity"]
}
]
}
"""
|> Jason.decode!()
~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)
payload_alpha =
"""
{
"a":{
"very":{
"nested":{
"heroes":[
{
"name":"Rorschach",
"age":35,
"secretIdentity":"Walter Joseph Kovacs",
"powers":[
"curiosity"
]
}
]
}
}
}
}
"""
|> Jason.decode!()
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
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])
squad = Nestru.decode_from_map!(payload, Squad)
squad_beta = Nestru.decode_from_map!(payload_beta, Squad, :beta)
squad_alpha = Nestru.decode_from_map!(payload_alpha, Squad, :alpha)
Repo.insert!(squad)
Repo.insert!(squad_beta)
squads = Squad |> Repo.all() |> Repo.preload(:members)
map = Nestru.encode_to_list_of_maps!(squads)
map |> Jason.encode!() |> IO.puts()
- 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/