# Source: https://mikro-orm.io/docs/serializing.md

# Serializing

By default, the ORM will define a `toJSON` method on all of your entity prototypes during discovery. This means that when you try to serialize your entity via `JSON.stringify()`, the ORM serialization will kick in automatically. The default implementation uses `EntityTransformer.toObject()` method, which converts an entity instance into a POJO. During this process, ORM specific constructs like the `Reference` or `Collection` wrappers are converted to their underlying values.

## Hidden Properties[​](#hidden-properties "Direct link to Hidden Properties")

If you want to omit some properties from serialized result, you can mark them with `hidden` flag on `@Property()` decorator. To have this information available on the type level, you also need to use the `HiddenProps` symbol:

```
@Entity()
class Book {

  // we use the `HiddenProps` symbol to define hidden properties on type level
  [HiddenProps]?: 'hiddenField' | 'otherHiddenField';

  @Property({ hidden: true })
  hiddenField = Date.now();

  @Property({ hidden: true, nullable: true })
  otherHiddenField?: string;

}

const book = new Book(...);
console.log(wrap(book).toObject().hiddenField); // undefined

// @ts-expect-error accessing `hiddenField` will fail to compile thanks to the `HiddenProps` symbol
console.log(wrap(book).toJSON().hiddenField); // undefined
```

Alternatively, you can use the `Hidden` type. It works the same as the `Opt` type (an alternative for `OptionalProps` symbol), and can be used in two ways:

* with generics: `hiddenField?: Hidden<string>;`
* with intersections: `hiddenField?: string & Hidden;`

Both will work the same, and can be combined with the `HiddenProps` symbol approach.

```
@Entity()
class Book {

  @Property({ hidden: true })
  hiddenField: Hidden<Date> = Date.now();

  @Property({ hidden: true, nullable: true })
  otherHiddenField?: string & Hidden;

}
```

## Shadow Properties[​](#shadow-properties "Direct link to Shadow Properties")

The opposite situation where you want to define a property that lives only in memory (is not persisted into database) can be solved by defining your property as `persist: false`. Such property can be assigned via one of `Entity.assign()`, `em.create()` and `em.merge()`. It will be also part of serialized result.

This can be handled when dealing with additional values selected via `QueryBuilder` or MongoDB's aggregations.

```
@Entity()
class Book {

  @Property({ persist: false })
  count?: number;

}

const book = new Book(...);
wrap(book).assign({ count: 123 });
console.log(wrap(book).toObject().count); // 123
console.log(wrap(book).toJSON().count); // 123
```

## Property Serializers[​](#property-serializers "Direct link to Property Serializers")

As an alternative to custom `toJSON()` method, we can also use property serializers. They allow to specify a callback that will be used when serializing a property:

```
@Entity()
class Book {

  @ManyToOne({ serializer: value => value.name, serializedName: 'authorName' })
  author: Author;

}

const author = new Author('God')
const book = new Book(author);
console.log(wrap(book).toJSON().authorName); // 'God'
```

## Implicit serialization[​](#implicit-serialization "Direct link to Implicit serialization")

Implicit serialization means calling `toObject()` or `toJSON()` on the entity, as opposed to explicitly using the `serialize()` helper. Since v6, it works entirely based on `populate` hints. This means that, unless you explicitly marked some entity as populated via `wrap(entity).populated()`, it will be part of the serialized form only if it was part of the `populate` hint:

```
// let's say both Author and Book entity has a m:1 relation to Publisher entity
// we only populate the publisher relation of the Book entity
const user = await em.findOneOrFail(Author, 1, {
  populate: ['books.publisher'],
});

const dto = wrap(user).toObject();
console.log(dto.publisher); // only the FK, e.g. `123`
console.log(dto.books[0].publisher); // populated, e.g. `{ id: 123, name: '...' }`
```

Moreover, the implicit serialization now respects the partial loading hints too. Previously, all loaded properties were serialized, partial loading worked only on the database query level. Since v6, we also prune the data on runtime. This means that unless the property is part of the partial loading hint (`fields` option), it won't be part of the DTO. Main difference here is the primary and foreign keys, that are often automatically selected as they are needed to build the entity graph, but will no longer be part of the DTO.

```
const user = await em.findOneOrFail(Author, 1, {
  fields: ['books.publisher.name'],
});

const dto = wrap(user).toObject();
// only the publisher's name will be available + primary keys
// `{ id: 1, books: [{ id: 2, publisher: { id: 3, name: '...' } }] }`
```

**This also works for embeddables, including nesting and object mode.**

Primary keys are automatically included. If you want to hide them, you have two options:

* use `hidden: true` in the property options
* use `serialization: { includePrimaryKeys: false }` in the ORM config

### Foreign keys and `forceObject`[​](#foreign-keys-and-forceobject "Direct link to foreign-keys-and-forceobject")

Unpopulated relations are serialized as foreign key values, e.g. `{ author: 1 }`, if you want to enforce objects, e.g. `{ author: { id: 1 } }`, use `serialization: { forceObject: true }` in your ORM config.

For strict typings to respect the global config option, you need to define it on your entity class via `Config` symbol:

```
import { Config, Entity, ManyToOne, PrimaryKey, Ref, wrap } from '@mikro-orm/core';

@Entity()
class Book {

  [Config]?: DefineConfig<{ forceObject: true }>;

  @PrimaryKey()
  id!: number;

  @ManyToOne(() => User, { ref: true })
  author!: Ref<User>;

}

const book = await em.findOneOrFail(Book, 1);
const dto = wrap(book).toObject();
const identityId = dto.author.id; // without the Config symbol, `dto.identity` would resolve to number
```

## Explicit serialization[​](#explicit-serialization "Direct link to Explicit serialization")

The serialization process is normally driven by the `populate` hints. If you want to take control over this, you can use the `serialize()` helper:

```
import { serialize } from '@mikro-orm/core';

const dtos = serialize([user1, user2]);
// [
//   { name: '...', books: [1, 2, 3], identity: 123 },
//   { name: '...', ... },
// ]

const [dto] = serialize(user1); // always returns an array
// { name: '...', books: [1, 2, 3], identity: 123 }

// for a single entity instance we can as well use `wrap(e).serialize()`
const dto2 = wrap(user1).serialize();
// { name: '...', books: [1, 2, 3], identity: 123 }
```

By default, every relation is considered as not populated - this will result in the foreign key values to be present. Loaded collections will be represented as arrays of the foreign keys. To control the shape of the serialized response we can use the second `options` parameter:

```
interface SerializeOptions<T extends object, P extends string = never, E extends string = never> {
  /** Specify which relation should be serialized as populated and which as a FK. */
  populate?: AutoPath<T, P>[] | boolean;

  /** Specify which properties should be omitted. */
  exclude?: AutoPath<T, E>[];

  /** Enforce unpopulated references to be returned as objects, e.g. `{ author: { id: 1 } }` instead of `{ author: 1 }`. */
  forceObject?: boolean;

  /** Ignore custom property serializers. */
  ignoreSerializers?: boolean;

  /** Include properties marked as `hidden`. */
  includeHidden?: boolean;

  /** Skip properties with `null` value. */
  skipNull?: boolean;

  /** Only include properties for a specific group. If a property does not specify any group, it will be included, otherwise only properties with a matching group are included. */
  groups?: string[];
}
```

Here is a more complex example:

```
import { wrap } from '@mikro-orm/core';

const dto = wrap(author).serialize({
  populate: ['books.author', 'books.publisher', 'favouriteBook'], // populate some relations
  exclude: ['books.author.email'], // skip property of some relation
  forceObject: true, // not populated or not initialized relations will result in object, e.g. `{ author: { id: 1 } }`
  skipNull: true, // properties with `null` value won't be part of the result
});
```

If you try to populate a relation that is not initialized, it will have same effect as the `forceObject` option - the value will be represented as object with just the primary key available.

### Serialization groups[​](#serialization-groups "Direct link to Serialization groups")

Every property can specify its serialization groups, which are then used with explicit serialization.

> Properties without the `groups` option are always included.

Let's consider the following entity:

```
@Entity()
class User {

  @PrimaryKey()
  id!: number;

  @Property()
  username!: string;

  @Property({ groups: ['public', 'private'] })
  name!: string;

  @Property({ groups: ['private'] })
  email!: string;

}
```

Now when you call `serialize()`:

* without the `groups` option, you get all the properties
* with `groups: ['public']` you get `id`, `username` and `name` properties
* with `groups: ['private']` you get `id`, `username`, `name` and `email` properties
* with `groups: []` you get only the `id` and `username` properties (those without groups)

```
const dto1 = serialize(user);
// User { id: 1, username: 'foo', name: 'Jon', email: 'jon@example.com' }

const dto2 = serialize(user, { groups: ['public'] });
// User { id: 1, username: 'foo', name: 'Jon' }
```

## Caching and `toPOJO`[​](#caching-and-topojo "Direct link to caching-and-topojo")

While `toObject` and `serialize` are often enough for serializing your entities, there is one use case where they often fall short, which is caching. When caching an entity, you usually want to ignore things like custom serializers or hidden properties. Once you try to load this entity from cache, it needs to have all the properties just like if you load it again.

Imagine the following scenario: you have a `User` entity that has a `password` property, which is `hidden: true`. Calling `toObject()` or `serialize()` would omit this hidden `password` property, while `toPOJO()` would keep it. If you want to cache such an entity, you want to have all the properties, not just those that are visible.

> The `toPOJO` method will also ignore serialization hints (`populate` and `fields`) and will expand all relations unless they form a cycle.

## Custom `toJSON` method[​](#custom-tojson-method "Direct link to custom-tojson-method")

You can provide custom implementation for `toJSON`, while using `toObject` for initial serialization:

```
@Entity()
class Book {

  // ...

  toJSON(strict = true, strip = ['id', 'email'], ...args: any[]): { [p: string]: any } {
    const o = wrap(this, true).toObject(...args); // do not forget to pass rest params here

    if (strict) {
      strip.forEach(k => delete o[k]);
    }

    return o;
  }

}
```

> Do not forget to pass rest params when calling `toObject(...args)`, otherwise the results might not be stable.