Transforming Field Names

Copy Markdown View Source

By default, AshJsonApi uses the Ash resource's attribute, relationship, calculation, and aggregate names directly as JSON:API field names. This means a :first_name attribute appears as "first_name" in requests and responses. The field_names and argument_names DSL options let you change this — for example, to expose a camelCase API while keeping snake_case internals.

These options affect every place a field or argument name appears: serialization output, request body parsing, sort and filter parameters, sparse fieldsets, error source pointers, relationship keys, JSON Schema, and OpenAPI spec generation.

Renaming fields

Built-in transformers

Use :camelize or :dasherize for common conventions:

field_names :camelize  # first_name → firstName
field_names :dasherize # first_name → first-name

Keyword list

Use a keyword list to rename specific fields:

json_api do
  type "user"

  field_names first_name: :firstName, last_name: :lastName
end

A GET /users/:id response would then return:

{
  "data": {
    "type": "user",
    "id": "...",
    "attributes": {
      "firstName": "Ada",
      "lastName": "Lovelace"
    }
  }
}

Fields not listed in the keyword list keep their original names.

Function

Use a 1-arity function for a blanket transformation. This is useful for converting all field names to camelCase:

json_api do
  type "user"

  field_names fn name ->
    camelized = name |> to_string() |> Macro.camelize()
    {first, rest} = String.split_at(camelized, 1)
    String.downcase(first) <> rest
  end
end

This applies to all public attributes, relationships, calculations, and aggregates on the resource.

Renaming action arguments

Action arguments (the values sent in the request body under data.attributes) can also be renamed with argument_names.

Keyword list

Provide a nested keyword list keyed by action name:

json_api do
  type "post"

  argument_names [
    create: [publish_at: :publishAt],
    update: [publish_at: :publishAt]
  ]
end

Function

Use a 2-arity function that receives (action_name, argument_name):

json_api do
  type "post"

  argument_names fn _action_name, arg_name ->
    camelized = arg_name |> to_string() |> Macro.camelize()
    {first, rest} = String.split_at(camelized, 1)
    String.downcase(first) <> rest
  end
end

The action_name parameter lets you apply different mappings per action if needed.

Where renaming is applied

Once configured, name mapping is applied consistently across:

  • Serialization — response attributes and relationships objects use the renamed keys.
  • Request body parsingdata.attributes keys in POST/PATCH bodies are expected under their renamed forms.
  • Sort parameters?sort=firstName works when :first_name is renamed to firstName.
  • Filter parameters?filter[firstName]=Ada maps back to the :first_name attribute via a ref transformer passed to Ash.Filter.parse_input/3.
  • Sparse fieldsets?fields[user]=firstName,lastName selects the renamed fields.
  • Error source pointers — validation errors point to /data/attributes/firstName instead of /data/attributes/first_name.
  • JSON Schema & OpenAPI — generated schemas use the renamed property names.

Combining both options

You can use field_names and argument_names together. A common pattern is to camelCase everything:

json_api do
  type "user"

  field_names :camelize
  argument_names :camelize
end