Skip to content

Commit

Permalink
fix(core): respect hidden properties of composite PKs during serializ…
Browse files Browse the repository at this point in the history
…ation

Closes #5203
  • Loading branch information
B4nan committed Feb 3, 2024
1 parent 42cc9cc commit 3d1cba3
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 9 deletions.
4 changes: 2 additions & 2 deletions docs/versioned_docs/version-6.0/custom-types.md
Expand Up @@ -291,8 +291,8 @@ export class CalendarDateArrayType extends ArrayType<CalendarDate> {

constructor() {
super(
date => ({ date }), // to JS
d => d.date, // to DB
date => ({ date }), // to JS
d => d.date, // to DB
);
}

Expand Down
21 changes: 18 additions & 3 deletions packages/core/src/serialization/EntitySerializer.ts
Expand Up @@ -202,16 +202,31 @@ export class EntitySerializer {
const wrapped = helper(child);
const populated = isPopulated(prop, options) && wrapped.isInitialized();
const expand = populated || !wrapped.__managed;
const meta = wrapped.__meta;
const childOptions = this.extractChildOptions(options, prop) as Dictionary;
const visible = meta.primaryKeys.filter(prop => isVisible(meta, prop, childOptions));

if (expand) {
return this.serialize(child, this.extractChildOptions(options, prop)) as EntityValue<T>;
return this.serialize(child, childOptions) as EntityValue<T>;
}

const pk = wrapped.getPrimaryKey(true)!;

if (options.forceObject || wrapped.__config.get('serialization').forceObject) {
return Utils.primaryKeyToObject(wrapped.__meta, wrapped.getPrimaryKey(true)!) as EntityValue<T>;
return Utils.primaryKeyToObject(meta, pk, visible) as EntityValue<T>;
}

if (Utils.isPlainObject(pk)) {
const pruned = Utils.primaryKeyToObject(meta, pk, visible) as EntityValue<T>;

if (visible.length === 1) {
return platform.normalizePrimaryKey(pruned[visible[0]] as IPrimaryKey) as EntityValue<T>;
}

return pruned;
}

return platform.normalizePrimaryKey(wrapped.getPrimaryKey() as IPrimaryKey) as EntityValue<T>;
return platform.normalizePrimaryKey(pk as IPrimaryKey) as EntityValue<T>;
}

private static processCollection<T extends object>(prop: EntityKey<T>, entity: T, options: SerializeOptions<T, any, any>): EntityValue<T> | undefined {
Expand Down
20 changes: 17 additions & 3 deletions packages/core/src/serialization/EntityTransformer.ts
Expand Up @@ -183,7 +183,9 @@ export class EntityTransformer {

private static processEntity<Entity extends object>(prop: keyof Entity, entity: Entity, platform: Platform, raw: boolean, populated: boolean): EntityValue<Entity> | undefined {
const child = entity[prop] as unknown as Entity | Reference<Entity>;
const wrapped = helper(child);
const wrapped = helper(child as Entity);
const meta = wrapped.__meta;
const visible = meta.primaryKeys.filter(prop => isVisible(meta, prop));

if (raw && wrapped.isInitialized() && child !== entity) {
return wrapped.toPOJO() as unknown as EntityValue<Entity>;
Expand All @@ -205,11 +207,23 @@ export class EntityTransformer {
return wrap(child).toJSON() as EntityValue<Entity>;
}

const pk = wrapped.getPrimaryKey(true)!;

if (wrapped.__config.get('serialization').forceObject) {
return Utils.primaryKeyToObject(wrapped.__meta, wrapped.getPrimaryKey(true)!) as EntityValue<Entity>;
return Utils.primaryKeyToObject(meta, pk, visible) as EntityValue<Entity>;
}

if (Utils.isPlainObject(pk)) {
const pruned = Utils.primaryKeyToObject(meta, pk, visible) as EntityValue<Entity>;

if (visible.length === 1) {
return platform.normalizePrimaryKey(pruned[visible[0]] as IPrimaryKey) as EntityValue<Entity>;
}

return pruned;
}

return platform.normalizePrimaryKey(wrapped.getPrimaryKey(true) as IPrimaryKey) as EntityValue<Entity>;
return platform.normalizePrimaryKey(pk as IPrimaryKey) as EntityValue<Entity>;
}

private static processCollection<Entity extends object>(prop: keyof Entity, entity: Entity, raw: boolean, populated: boolean): EntityValue<Entity> | undefined {
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/utils/Utils.ts
Expand Up @@ -1277,13 +1277,17 @@ export class Utils {
return typeof value === 'object' && !!value && '__raw' in value;
}

static primaryKeyToObject<T>(meta: EntityMetadata<T>, primaryKey: Primary<T> | T) {
static primaryKeyToObject<T>(meta: EntityMetadata<T>, primaryKey: Primary<T> | T, visible?: (keyof T)[]) {
const pks = meta.compositePK && Utils.isPlainObject(primaryKey) ? Object.values(primaryKey) : Utils.asArray(primaryKey);
const pkProps = meta.getPrimaryProps();

return meta.primaryKeys.reduce((o, pk, idx) => {
const pkProp = pkProps[idx];

if (visible && !visible.includes(pkProp.name)) {
return o;
}

if (Utils.isPlainObject(pks[idx]) && pkProp.targetMeta) {
o[pk] = Utils.getOrderedPrimaryKeys(pks[idx], pkProp.targetMeta) as any;
return o;
Expand Down
57 changes: 57 additions & 0 deletions tests/features/serialization/GH5203.test.ts
@@ -0,0 +1,57 @@
import { Entity, MikroORM, PrimaryKey, OneToOne, Ref, ref, serialize, wrap } from '@mikro-orm/sqlite';

@Entity()
class Parent {

@PrimaryKey()
id!: string;

// Tenant ID should be hidden from user
@PrimaryKey({ hidden: true })
tenant!: string;

}

@Entity()
class Child {

@PrimaryKey()
id!: string;

@OneToOne(() => Parent, { ref: true, nullable: true })
parent!: Ref<Parent>;

}

let orm: MikroORM;

beforeAll(async () => {
orm = await MikroORM.init({
entities: [Parent, Child],
dbName: ':memory:',
});
await orm.schema.createSchema();
});

afterAll(() => orm.close());

test('explicit serialization', async () => {
const child = orm.em.create(Child, {
id: 'child_id',
parent: ref(Parent, { id: 'parent_id', tenant: 'tenant_id' }),
});

expect(serialize(child)).toEqual({ id: 'child_id', parent: 'parent_id' });
expect(serialize(child, { forceObject: true })).toEqual({ id: 'child_id', parent: { id: 'parent_id' } });
});

test('implicit serialization', async () => {
const child = orm.em.create(Child, {
id: 'child_id',
parent: ref(Parent, { id: 'parent_id', tenant: 'tenant_id' }),
});

expect(wrap(child).toObject()).toEqual({ id: 'child_id', parent: 'parent_id' });
orm.config.get('serialization').forceObject = true;
expect(wrap(child).toObject()).toEqual({ id: 'child_id', parent: { id: 'parent_id' } });
});

0 comments on commit 3d1cba3

Please sign in to comment.