Packages

Strictly-typed builder for generating SEO Schema.org JSON-LD in Elixir and Phoenix applications. 1000+ generated struct modules — build with struct literals for first-class editor auto-complete and compile-time field checks.

Current section

Files

Jump to
schema_org lib schema_org.ex
Raw

lib/schema_org.ex

defmodule SchemaOrg do
@moduledoc """
Strictly-typed builder for Schema.org JSON-LD.
Each Schema.org Class is a generated struct module under `SchemaOrg.*`
(e.g. `SchemaOrg.Product`, `SchemaOrg.Offer`). Build a graph with ordinary
struct literals — your editor auto-completes the valid fields and the compiler
rejects the rest — then serialise it with `to_json_ld/1`.
%SchemaOrg.Product{
name: "MacBook Pro",
offers: %SchemaOrg.Offer{price: 1999.0}
}
|> SchemaOrg.to_map()
#=> %{
# "@type" => "Product",
# "name" => "MacBook Pro",
# "offers" => %{"@type" => "Offer", "price" => 1999.0}
# }
A property field is untyped, so it accepts Schema.org's loose value model
directly: a scalar or a nested struct (`brand: "Apple"` or
`brand: %SchemaOrg.Brand{}`), and a single value or a list
(`offers: %SchemaOrg.Offer{}` or `offers: [%SchemaOrg.Offer{}, ...]`).
"""
@context "https://schema.org"
@doc """
Serialises a generated SchemaOrg struct into a JSON-LD string.
Adds the top-level `@context`, recurses into nested structs, drops unset
(`nil`) properties, and re-keys each field to its Schema.org camelCase name.
"""
@spec to_json_ld(struct()) :: String.t()
def to_json_ld(%_{} = thing) do
thing
|> to_map()
|> Map.put("@context", @context)
|> Jason.encode!()
end
@doc """
Like `to_json_ld/1` but returns the bare JSON-LD map (no `@context`, not encoded).
Useful for embedding inside a larger document or for assertions in tests.
"""
@spec to_map(struct()) :: map()
def to_map(%mod{} = thing) do
meta = mod.__schema_org__()
thing
|> Map.from_struct()
|> Enum.reduce(%{"@type" => meta.type}, fn
{_field, nil}, acc ->
acc
{field, value}, acc ->
Map.put(acc, Map.fetch!(meta.json_keys, field), encode_value(value))
end)
end
# Recurse into nested SchemaOrg structs and lists; pass scalars through.
defp encode_value(list) when is_list(list), do: Enum.map(list, &encode_value/1)
defp encode_value(%mod{} = value) do
if schema_struct?(mod), do: to_map(value), else: value
end
defp encode_value(value), do: value
@doc """
Returns `true` if `module` is a generated SchemaOrg type module.
"""
@spec schema_struct?(module()) :: boolean()
def schema_struct?(module) do
Code.ensure_loaded?(module) and function_exported?(module, :__schema_org__, 0)
end
end