Every module that calls use Trogon.ObjectId or use Trogon.UnionObjectId has protocol implementations generated into it. The implementations live in your module, not in this library, so they do not appear in this documentation as modules of their own.

Emitted format

Each protocol emits one of two forms: the full form, #{prefix}#{separator}#{id}, or the bare form, id with no prefix.

ProtocolEmitsControlled by
String.CharsFullNothing, always full
Jason.EncoderFull or bare:json_format
JSON.EncoderFull or bare:json_format
Ecto.Type.dump/1 and to_storage/1Full or bare:storage_format
Phoenix.ParamFullNothing, always full
Phoenix.HTML.SafeFull, HTML escapedNothing, always full

Given a type declared with json_format: :drop_prefix:

defmodule MyApp.UserId do
  use Trogon.ObjectId, object_type: "user", json_format: :drop_prefix
end

user_id = MyApp.UserId.new!("abc-123")

to_string(user_id)                 #=> "user_abc-123"
JSON.encode!(user_id)              #=> "\"abc-123\""
Jason.encode!(user_id)             #=> "\"abc-123\""
Phoenix.Param.to_param(user_id)    #=> "user_abc-123"

Why Phoenix.Param ignores :json_format

Phoenix.Param.to_param/1 output has to survive a roundtrip back into your application, and both parse/1 and Ecto.Type.cast/1 accept only the full form. A param emitted in the bare form would fail to parse on the way back in, so Phoenix.Param always emits the full form regardless of how the type is configured for JSON.

This matters even if you never render an object id into a URL yourself. Phoenix.Param sets @fallback_to_any true, and its Any implementation matches any struct carrying an :id key, which every object id does. Without a generated implementation, ~p"/users/#{user_id}" would silently produce /users/abc-123 rather than /users/user_abc-123, with no error raised at the point the wrong value was produced.

Optional dependencies

Three of the protocols come from optional packages. Each implementation is wrapped in a Code.ensure_loaded?/1 check that is evaluated when your module is compiled, not when this library is compiled.

ProtocolRequires
Jason.Encoder:jason
JSON.EncoderElixir 1.18 or later
Phoenix.Param:phoenix
Phoenix.HTML.Safe:phoenix_html

If you add one of these packages after your object id modules have already been compiled, recompile them so the implementation is generated:

$ mix deps.compile --force my_app

Jason.Encoder and JSON.Encoder read the same :json_format option and are generated together, so a type encodes identically no matter which of the two a caller reaches for.

Union types

A Trogon.UnionObjectId delegates every protocol to the member it wraps, so the member's own :json_format decides the encoded form:

defmodule MyApp.PrincipalId do
  use Trogon.UnionObjectId, types: [MyApp.UserId, MyApp.SystemId]
end

principal = MyApp.PrincipalId.new(MyApp.UserId.new!("abc-123"))

JSON.encode!(principal)           #=> "\"abc-123\""
Phoenix.Param.to_param(principal) #=> "user_abc-123"