> For the complete documentation index, see [llms.txt](https://bxorm.ortusbooks.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bxorm.ortusbooks.com/bxorm-2.x/working-with-entities/structs.md).

# Entities as Structs

Turn entities into structs for JSON APIs and views, with or without loading them

JSON APIs and views usually need plain structs, not entities. bx-orm builds them three ways, with the same rules and the same output:

| Use                                                                                                                                      | When                                                                 |
| ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| [entityToStruct( entityOrArray, \[options\] )](/bxorm-2.x/reference/built-in-functions/entitytostruct.md)                                | You already have the entities                                        |
| [entityLoadAsStruct( entityName, idOrFilter, \[includes\], \[options\] )](/bxorm-2.x/reference/built-in-functions/entityloadasstruct.md) | Load by id or filter straight into structs, without loading entities |
| [`asStruct( includes, [options] )`](/bxorm-2.x/working-with-entities/criteria.md#structs-with-includes) on `entityCriteria()`            | Any criteria query, straight into structs                            |

```js
user  = entityToStruct( entityLoadByPK( "User", 42 ), { includes : "role.name" } );
user  = entityLoadAsStruct( "User", 42, "role.name" );
users = entityCriteria( "User" ).isEq( "active", true ).asStruct( "role.name" ).list();
```

Need a `Query` object instead? See [Entities as Queries](/bxorm-2.x/working-with-entities/queries.md).

The rules follow [mementifier](https://forgebox.io/view/mementifier), so an entity's existing `this.memento` works as is.

## What goes in the struct

`entityToStruct()` reads each value through the entity's getter when it has one, else from the entity's variables scope. BoxLang gives every persistent property an implicit getter, so a getter you write yourself (for example `getEmail()` returning `lcase( variables.email )`) replaces it and shapes the output.

By default a struct holds the entity's id and its plain properties (columns, version and timestamp properties), not its associations. The options change that:

| Option           | Meaning                                                                                                                                                                                                                                  |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `includes`       | What to add, as a list or an array: property names, getter names (without `get`), or dotted association paths such as `role.name`. `"lastLoginTime:lastLogin"` writes the value under another key. `"*"` stands for every plain property |
| `excludes`       | What to leave out, also with dotted paths: `"passwordHash,orders.notes"`                                                                                                                                                                 |
| `mappers`        | A struct of key to `( value, memento ) => newValue`. See [Mappers](#mappers)                                                                                                                                                             |
| `defaults`       | A struct of key to the value to use when the value is null                                                                                                                                                                               |
| `ignoreDefaults` | Ignore the entity's `this.memento` default includes and excludes                                                                                                                                                                         |
| `profile`        | Use one of the entity's `this.memento.profiles`                                                                                                                                                                                          |

`entityLoadAsStruct()` takes `includes` as its own argument and the rest in its options.

Other rules:

* A null value becomes the matching `defaults` entry, otherwise an empty string. A null collection becomes an empty array.
* Dates are ISO 8601 strings, so every struct serializes the same way: date-times as `yyyy-MM-ddTHH:mm:ss` plus the offset (`2024-03-05T10:20:30Z`), dates as `yyyy-MM-dd` and times as `HH:mm:ss`.
* An include that is neither a property, a getter nor a mapper key is an `orm.property.unknown` error that suggests the right name. (mementifier skips it silently.)

## Associations

An association is written as a struct (to-one) or an array of structs (to-many):

```js
entityToStruct( author, { includes : "posts" } );
// { id : 901, firstName : "Ann", ..., posts : [ { id : 901, title : "First" }, { id : 902, title : "Second" } ] }

entityToStruct( post, { includes : "author.firstName" } );
// { id : 902, title : "Second", author : { firstName : "Ann" } }
```

* An association included on its own (`"posts"`) uses the associated entity's own defaults (its `this.memento`, or its id and plain properties).
* With paths below it (`"author.firstName"`), only those fields are written.
* Paths can go deeper: `"orders.items.product.name"`.
* Excludes work the same way: `"posts.title"` leaves the title out of each post.
* An entity that is already being written higher up the same branch is written as its id, so cycles end: `entityToStruct( author, { includes : "posts.author" } )` writes each post's `author` as `901`.

## Mappers

A mapper is called with the value and the struct being built, and returns the new value. Mappers run last, once every key is filled, on the keys the struct holds:

```js
entityToStruct( user, {
    mappers : {
        email : ( value ) => lcase( value ),
        name  : ( value, memento ) => memento.firstName & " " & memento.lastName
    },
    includes : "name"
} );
```

An include that is neither a property nor a getter, but has a mapper, is a computed key: the mapper builds it (above, `name`).

The caller's `mappers` and `defaults` apply to the top-level entity. Associated entities use their own `this.memento`.

## `this.memento`

An entity describes its default struct with mementifier's `this.memento`:

```js
class persistent="true" table="authors" {
    property name="id" fieldtype="id" generator="increment";
    property name="firstName";
    property name="lastName";
    property name="nickName";
    property name="passwordHash";
    property name="joined" ormType="timestamp";
    property name="posts" fieldtype="one-to-many" cfc="Post" fkcolumn="author_id" inverse="true";

    this.memento = {
        defaultIncludes : [ "id", "firstName", "lastName", "nickName", "fullName", "joined", "initials" ],
        defaultExcludes : [],
        neverInclude    : [ "passwordHash" ],
        defaults        : { nickName : "none" },
        mappers         : {
            lastName : ( value, memento ) => ucase( value ),
            initials : ( value, memento ) => left( memento.firstName, 1 ) & left( memento.lastName, 1 )
        },
        profiles        : {
            export : { defaultIncludes : [ "id", "lastName" ], mappers : {} }
        }
    };

    function getFullName() {
        return getFirstName() & " " & getLastName();
    }
}
```

```js
entityToStruct( author );
// { id : 901, firstName : "Ann", lastName : "BAKER", nickName : "none", fullName : "Ann Baker",
//   joined : "2024-03-05T10:20:30Z", initials : "AB" }
```

| Key               | Meaning                                                                                                            |
| ----------------- | ------------------------------------------------------------------------------------------------------------------ |
| `defaultIncludes` | What the struct holds by default. Without it: the id and plain properties, and the caller's `includes` add to them |
| `defaultExcludes` | What is left out by default                                                                                        |
| `neverInclude`    | What is never written, even when the caller includes it                                                            |
| `defaults`        | Values for nulls                                                                                                   |
| `mappers`         | Mappers, as above. The caller's mappers replace one for the same key                                               |
| `profiles`        | Named variations, see below                                                                                        |

The caller's `includes` and `excludes` add to the defaults. `ignoreDefaults : true` drops `defaultIncludes` and `defaultExcludes` (`neverInclude` still applies).

### Profiles

A profile is a named set of `this.memento` keys. With `{ profile : "export" }`, the keys the profile defines replace the base ones, and the other base keys stay. The profile applies down the whole graph, to every associated entity that defines it:

```js
entityToStruct( author, { profile : "export" } );
// { id : 901, lastName : "Baker" }   (the profile's mappers are empty, so lastName is not upper-cased)
```

## Without loading entities

`entityLoadAsStruct()` and criteria `asStruct( includes )` read the structs with projection queries, so no entity is loaded into the session:

* One query reads the plain values of the root entity and of every to-one association in the includes (left joins, so a missing association is written as its default, an empty string).
* Each to-many association is one more query, limited to the ids just read, and its rows are grouped back into their parents. Collections come back in id order.

The output matches `entityToStruct()` for the same includes, getters included: when a property has a getter you wrote (such as `getEmail()` above), the row's plain values are copied into a scratch instance of the entity and the getter is called on it. The getter can read the entity's other plain properties; associations are not loaded there, so a getter that walks an association needs `entityToStruct()`. What cannot be read without an entity:

* **Getter-only includes** (a getter that is not a property, such as `fullName`): one listed in the entity's `this.memento` defaults is left out; one you include yourself is an `orm.argument` error. Compute the key with a mapper instead, or use `entityToStruct()`.
* **Value collections** (`fieldtype="collection"`) and **entities with a composite id** are `orm.argument` errors.

```js
// by id: a struct, or null
user = entityLoadAsStruct( "User", 42, "role.name,orders" );

// by filter: an array of structs
users = entityLoadAsStruct( "User", { active : true }, "", { sortOrder : "lastName", maxResults : 20, offset : 40 } );

// by filter, one struct (several matches: orm.query.nonUnique)
user = entityLoadAsStruct( "User", { email : "ann@example.com" }, "", { unique : true } );

// any criteria query: list(), get(), first() or paginate()
page = entityCriteria( "User" )
    .isEq( "active", true )
    .order( "lastName" )
    .asStruct( "role.name", { profile : "list" } )
    .paginate( page = 2, maxRows = 25 );
```

`asStruct()` without arguments is different: it returns the plain columns of each row (or the projections), with ISO 8601 dates, and does not use `this.memento`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://bxorm.ortusbooks.com/bxorm-2.x/working-with-entities/structs.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
