@mikro-orm/core
Version:
TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.
233 lines (232 loc) • 9.41 kB
JavaScript
import { helper, wrap } from '../entity/wrap.js';
import { Utils } from '../utils/Utils.js';
import { ReferenceKind } from '../enums.js';
import { SerializationContext } from './SerializationContext.js';
import { isRaw } from '../utils/RawQueryFragment.js';
function isVisible(meta, propName, ignoreFields = []) {
const prop = meta.properties[propName];
const visible = prop && !prop.hidden;
const prefixed = prop && !prop.primary && !prop.accessor && propName.startsWith('_'); // ignore prefixed properties, if it's not a PK
return visible && !prefixed && !ignoreFields.includes(propName);
}
/** Converts entity instances to plain objects via `toObject()`, respecting populate hints, hidden fields, and serialization context. */
export class EntityTransformer {
/** Converts an entity to a plain object, respecting populate hints, hidden fields, and custom serializers. */
static toObject(entity, ignoreFields = [], raw = false) {
if (!Array.isArray(ignoreFields)) {
ignoreFields = [];
}
const wrapped = helper(entity);
let contextCreated = false;
if (!wrapped) {
return entity;
}
if (!wrapped.__serializationContext.root) {
const root = new SerializationContext(wrapped.__serializationContext.populate, wrapped.__serializationContext.fields, wrapped.__serializationContext.exclude);
SerializationContext.propagate(root, entity, isVisible);
contextCreated = true;
}
const root = wrapped.__serializationContext.root;
const meta = wrapped.__meta;
const ret = {};
const props = new Set();
if (meta.serializedPrimaryKey && !meta.compositePK) {
props.add(meta.serializedPrimaryKey);
}
else {
meta.primaryKeys.forEach(pk => props.add(pk));
}
if (wrapped.isInitialized() || !wrapped.hasPrimaryKey()) {
const entityKeys = new Set(Object.keys(entity));
for (const prop of meta.props) {
if (entityKeys.has(prop.name) || (prop.getter && prop.accessor === prop.name)) {
props.add(prop.name);
}
}
for (const key of entityKeys) {
if (!meta.properties[key]) {
props.add(key);
}
}
}
const visited = root.visited.has(entity);
const includePrimaryKeys = wrapped.__config.get('serialization').includePrimaryKeys;
if (!visited) {
root.visited.add(entity);
}
for (const prop of props) {
const visible = raw ? meta.properties[prop] : isVisible(meta, prop, ignoreFields);
if (!visible) {
continue;
}
const populated = root.isMarkedAsPopulated(meta.class, prop);
if (!raw) {
const partiallyLoaded = root.isPartiallyLoaded(meta.class, prop);
const isPrimary = includePrimaryKeys && meta.properties[prop].primary;
if (!partiallyLoaded && !populated && !isPrimary) {
continue;
}
if (root.isExcluded(meta.class, prop) && !populated) {
continue;
}
}
const cycle = root.visit(meta.class, prop);
if (cycle && visited) {
continue;
}
const val = EntityTransformer.processProperty(prop, entity, raw, populated);
if (!cycle) {
root.leave(meta.class, prop);
}
if (isRaw(val)) {
throw new Error(`Trying to serialize raw SQL fragment: '${val.sql}'`);
}
if (typeof val === 'undefined') {
continue;
}
ret[this.propertyName(meta, prop, raw)] = val;
}
if (!wrapped.isInitialized() && wrapped.hasPrimaryKey()) {
return ret;
}
for (const prop of meta.getterProps) {
// decorated get methods
if (prop.getterName != null) {
const visible = !prop.hidden && entity[prop.getterName] instanceof Function;
const populated = root.isMarkedAsPopulated(meta.class, prop.name);
if (visible) {
ret[this.propertyName(meta, prop.name, raw)] = this.processProperty(prop.getterName, entity, raw, populated);
}
}
else {
// decorated getters
const visible = !prop.hidden && typeof entity[prop.name] !== 'undefined';
const populated = root.isMarkedAsPopulated(meta.class, prop.name);
if (visible) {
ret[this.propertyName(meta, prop.name, raw)] = this.processProperty(prop.name, entity, raw, populated);
}
}
}
if (contextCreated) {
root.close();
}
return ret;
}
static propertyName(meta, prop, raw) {
if (raw) {
return prop;
}
if (meta.properties[prop].serializedName) {
return meta.properties[prop].serializedName;
}
if (meta.properties[prop].primary && meta.serializedPrimaryKey) {
return meta.serializedPrimaryKey;
}
return prop;
}
static processProperty(prop, entity, raw, populated) {
const wrapped = helper(entity);
const property = wrapped.__meta.properties[prop] ?? { name: prop };
const serializer = property?.serializer;
const value = entity[prop];
// getter method
if (entity[prop] instanceof Function) {
const returnValue = entity[prop]();
if (serializer && !raw) {
return serializer(returnValue);
}
return returnValue;
}
if (serializer && !raw) {
return serializer(value);
}
if (Utils.isCollection(value)) {
return EntityTransformer.processCollection(property, entity, raw, populated);
}
if (Utils.isEntity(value, true)) {
return EntityTransformer.processEntity(property, entity, wrapped.__platform, raw, populated);
}
if (Utils.isScalarReference(value)) {
return value.unwrap();
}
if (property.kind === ReferenceKind.EMBEDDED) {
if (Array.isArray(value)) {
return value.map(item => {
const wrapped = item && helper(item);
return wrapped ? wrapped.toJSON() : item;
});
}
const wrapped = value && helper(value);
return wrapped ? wrapped.toJSON() : value;
}
const customType = property?.customType;
if (customType) {
return customType.toJSON(value, wrapped.__platform);
}
if (property?.primary) {
return wrapped.__platform.normalizePrimaryKey(value);
}
return value;
}
static processEntity(prop, entity, platform, raw, populated) {
const child = entity[prop.name];
const wrapped = helper(child);
const meta = wrapped.__meta;
const visible = meta.primaryKeys.filter(prop => isVisible(meta, prop));
if (raw && wrapped.isInitialized() && child !== entity) {
return wrapped.toPOJO();
}
function isPopulated() {
if (wrapped.__populated != null) {
return wrapped.__populated;
}
if (populated) {
return true;
}
return !wrapped.__managed;
}
if (wrapped.isInitialized() && isPopulated() && child !== entity) {
return wrap(child).toJSON();
}
let pk = wrapped.getPrimaryKey();
if (prop.customType) {
pk = prop.customType.toJSON(pk, wrapped.__platform);
}
if (wrapped.__config.get('serialization').forceObject) {
return Utils.primaryKeyToObject(meta, pk, visible);
}
if (Utils.isPlainObject(pk)) {
const pruned = Utils.primaryKeyToObject(meta, pk, visible);
if (visible.length === 1) {
return platform.normalizePrimaryKey(pruned[visible[0]]);
}
return pruned;
}
return platform.normalizePrimaryKey(pk);
}
static processCollection(prop, entity, raw, populated) {
const col = entity[prop.name];
if (raw && col.isInitialized(true)) {
return col.map(item => helper(item).toPOJO());
}
if (col.shouldPopulate(populated)) {
return col.toArray();
}
if (col.isInitialized()) {
const wrapped = helper(entity);
const forceObject = wrapped.__config.get('serialization').forceObject;
return col.map(item => {
const wrapped = helper(item);
const pk = wrapped.getPrimaryKey();
if (prop.customType) {
return prop.customType.toJSON(pk, wrapped.__platform);
}
if (forceObject) {
return Utils.primaryKeyToObject(wrapped.__meta, pk);
}
return pk;
});
}
return undefined;
}
}