# Attributes

# Introduction

The attributes member on a resource object represents information about the model that it is serializing. Typically this will be the values of database columns, excluding those that represent relationships.

Attributes may contain any JSON value.

Complex data structures involving JSON objects and array are allowed as attribute values. However, any object that constitutes or is contained in an attribute must not contain a relationships or links member.

# Field Names

The members of a resource's attributes and relationships objects are collectively called a resource object's fields. Fields share common namespace with each other and the type and id members. In other words, a resource cannot have an attribute or relationship with the same name, nor can it have an attribute or relationship named type or id.

# Field Name Case

The JSON:API specification places hard restrictions (opens new window) on what names you are allowed to use for the attributes member names.

To summarize, you are allowed snake-case, camel-case and dash-case member name. However, the specification does recommend that camel-case is used. (opens new window)

# Defining Attributes

To add attributes to your resource object, list them in the attributes method. For example, a posts resource might look like this:

namespace App\JsonApi\V1\Posts;

use LaravelJsonApi\Core\Resources\JsonApiResource;

class PostResource extends JsonApiResource
{

    /**
     * Get the resource's attributes.
     *
     * @param \Illuminate\Http\Request|null $request
     * @return iterable
     */
    public function attributes($request): iterable
    {
        return [
            'content' => $this->content,
            'createdAt' => $this->created_at,
            'slug' => $this->slug,
            'synopsis' => $this->synopsis,
            'title' => $this->title,
            'updatedAt' => $this->updated_at,
        ];
    }

    // ...

}

Notice that we can access model properties from the $this variable. This is because a resource class will automatically proxy property and method access down to the underlying model for convenient access.

# Conditional Attributes

Sometimes you may wish to only include an attribute in a resource response if a given condition is met. For example, you may wish to only include a value if the current user is an "administrator". Our API resource class provides a variety of helper methods to assist you in this situation.

The when method may be used to conditionally add an attribute to a resource response. In the following example, the secret member will only be serialized in the attributes of a users resource if the authenticated user's isAdmin method returns true:

public function attributes($request): iterable
{
    return [
        'createdAt' => $this->created_at,
        'email' => $this->email,
        'name' => $this->name,
        'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),
        'updatedAt' => $this->updated_at,
    ];
}

The when method also accepts a Closure as its second argument, allowing you to only calculate the resulting value if the given condition is true:

'secret' => $this->when($request->user()->isAdmin(), fn() => 'secret-value'),

# Merging Conditional Attributes

Sometimes you may have several attributes that should only be included in the resource object based on the same condition. In this case, you may use the mergeWhen method to include the attributes only when the given condition is true:

public function attributes($request): iterable
{
    return [
        'createdAt' => $this->created_at,
        'email' => $this->email,
        'name' => $this->name,
        $this->mergeWhen($request->user()->isAdmin(), [
            'firstSecret' => 'value',
            'secondSecret' => 'value',
        ]),
        'updatedAt' => $this->updated_at,
    ];
}

Again, if the given condition is false, these attributes will not appear in the serialized JSON:API resource object.

The mergeWhen method also allows values to be Closure instances, allowing you to only calculate values if they will be in the serialized resource object:

$this->mergeWhen($request->user()->isAdmin(), [
    'firstSecret' => fn() => 'value',
    'secondSecret' => 'value',
]),

And finally, the mergeWhen method also accepts a Closure as its second argument:

$this->mergeWhen($request->user()->isAdmin(), fn() => [
    'firstSecret' => 'value',
    'secondSecret' => 'value',
]),

# Array Attributes

In JSON there is a distinction between array lists (zero-indexed arrays), and JSON objects (PHP associative arrays). It is important for API clients that values are returned in a consistent format.

# Array Lists

Always ensure that array lists are serialized as zero-indexed arrays. If you use Laravel's Collection class to process arrays when serializing them, it is important to remember that some methods, such as filter or reject, may result in the array having non-sequential keys.

For example, let's say our users resource has a permissions attribute that we want to be a JSON array of string permissions. Given the following:

'permissons' => collect($this->permissions)
  ->reject('bar')
  ->all(),

If the value of permissions was ['foo', 'bar', 'baz'], the above example would result in a JSON object: {"0":"foo","2":"baz"}. This would be unexpected for the client, that expects a zero-indexed JSON array.

To fix, we should call the values method before all:

'permissons' => collect($this->permissions)
  ->reject('bar')
  ->values()
  ->all(),

# Associative Arrays

When serializing PHP associative arrays, there are two things to consider: avoiding empty values and being consistent with the key case.

To illustrate both, let's say our users resource has an options attribute. In PHP this is an associative array stored in a JSON database column.

# Empty Values

If the array stored in our database column is empty, then the following attribute:

'options' => $this->options,

Would actually be serialized as an empty JSON array, i.e. []. This would be an unexpected value for the client, which is expecting the options attribute to be a JSON object.

To fix, we should cast the value to null if it is empty:

'options' => $this->options ?: null,

# Key Case

When serializing associative arrays, you should also consider the key case that is used.

Say for example our users resource follows the JSON:API recommendation of camel-case member names. It would make sense for the options JSON object to also use camel-case keys. However, when storing the options we might be following the Eloquent convention of snake-casing key names.

In such a scenario we would need to transform the keys of the options array when serializing it. Luckily we provide some array helpers to recursively transform array keys. For example:

use LaravelJsonApi\Core\Support\Arr;

'options' => Arr::camelize($this->options) ?: null,

Our Arr helper class has the following methods to recursively transform keys:

  • camelize: recursively camel-case all keys in the provided array.
  • dasherize: recursively dash-case all keys in the provided array.
  • underscore: recursively convert camel-case and dash-case keys to underscore (snake) case.

TIP

The underscore method differs from Laravel's snake case, in that it will convert both camel-cased and dasherized strings to snake case with a _ delimiter.

Our Arr helper also forwards calls to the Illuminate\Support\Arr class, meaning you do not need to import both classes if you want to use methods on both.

Last Updated: 2/12/2022, 1:27:17 PM