documentation/topics/field-names.md

<!--
SPDX-FileCopyrightText: 2020 Zach Daniel

SPDX-License-Identifier: MIT
-->

# Transforming Field Names

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:

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

### Keyword list

Use a keyword list to rename specific fields:

```elixir
json_api do
  type "user"

  field_names first_name: :firstName, last_name: :lastName
end
```

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

```json
{
  "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:

```elixir
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:

```elixir
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)`:

```elixir
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 parsing** — `data.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:

```elixir
json_api do
  type "user"

  field_names :camelize
  argument_names :camelize
end
```