-
-
Notifications
You must be signed in to change notification settings - Fork 611
Closed
Description
Describe the bug
I try to save a runtime variable of type string | number | Date | undefined as single entity property by using the following custom type to transform it to JSON { value: 'example123', type: 'string'} and back:
export class ValueType extends Type<string | number | Date | undefined, string> {
convertToDatabaseValue(value: string | number | Date | undefined): string {
if (value === undefined || value === null) {
return JSON.stringify({ value: null, type: 'null' });
}
if (typeof value === 'string') {
return JSON.stringify({ value, type: 'string' });
}
if (typeof value === 'number') {
return JSON.stringify({ value, type: 'number' });
}
if (value instanceof Date) {
return JSON.stringify({ value: value.toISOString(), type: 'date' });
}
throw ValidationError.invalidType(ValueType, value, 'JS');
}
convertToJSValue(
value: string | { value: any; type: string } | null | undefined,
plattform: Platform,
): string | number | Date | undefined {
if (value === undefined || value === null) {
return undefined;
}
if (typeof value === 'object' && 'type' in value && 'value' in value) {
return this._fromJson(value);
}
if (typeof value === 'string') {
try {
const json = JSON.parse(value);
return this._fromJson(json);
} catch (e) {
return value;
}
}
throw ValidationError.invalidType(ValueType, value, 'database');
}
private _fromJson(json: { value: any; type: string }): string | number | Date | undefined {
if (json.type === 'string') return json.value;
if (json.type === 'number') return json.value;
if (json.type === 'date') return json.value ? new Date(json.value) : undefined;
if (json.type === 'null') return undefined;
throw ValidationError.invalidType(ValueType, json, 'database');
}
getColumnType(prop: EntityProperty, platform: Platform) {
return platform.getJsonDeclarationSQL?.() || 'jsonb';
}
}
Motivation behind is to have a single searchable property/column instead of one column per data type or string column + helper column for type information (even if the later would be acceptable if the parsing could be done automatically but currently it seems to be not possible?).
Issues:
- It works well when the custom type output (jsonb) is saved as dedicated column (entity property or embeddable with option
{ object: false } - It works partially when the custom type is part of an object-based embeddable (see reproduction example OutputRuleCalculation). The custom type json gets saved as part of the embeddable json and gets also parsed back via custom type (I guess thanks to Custom type not working inside Embeddable #1191). But querying by this field does not show any results (feat(core): refactor merging to allow querying by custom type #800 covered only column-based embeddable fields?).
- It works not at all when the embeddable is embedded as array (
{ array: true }, see reproduction example OutputRuleCondition). In this case the provided runtime value will get saved as is without any custom type magic parsing it. In addition, querying by this field doesn't work either.
Reproduction
- Use my provided custom type from above
- Use my 2 embeddables and entity:
@Embeddable()export class OutputRuleCondition {
@Property({ type: 'text', name: 'operator' })
public operator!: string;
@Property({
type: ValueType,
nullable: true,
})
public comparisonValue?: string | number | Date;
}
@Embeddable()
export class OutputRuleCalculation {
@Property({ type: 'text' })
public calculationType!: string;
@Property({ type: ValueType, nullable: true })
public constant?: string | number | Date;
}
@Entity({ tableName: 'example' })
export class OutputRule {
@PrimaryKey({ type: 'uuid' })
public id!: string;
@Property({ type: 'boolean' })
public isDefault!: boolean;
@Embedded(() => OutputRuleCalculation, { object: true })
public calculation!: OutputRuleCalculation;
@Embedded(() => OutputRuleCondition, { array: true })
public conditions: OutputRuleCondition[] = [];
}
- Save some values in DB:
const outputRule = new OutputRule();
outputRule.id = 'b2e1e6b2-1234-4c1a-9c2a-abcdefabcdef';
outputRule.isDefault = true;
outputRule.calculation = new OutputRuleCalculation();
outputRule.calculation.calculationType = 'constant';
outputRule.calculation.constant = 42; // could also be a string, Date or undefined
outputRule.conditions = [
Object.assign(new OutputRuleCondition(), {
operator: 'eq',
comparisonValue: 'foo',
}),
Object.assign(new OutputRuleCondition(), {
operator: 'gt',
comparisonValue: new Date('2024-01-01T00:00:00Z'),
}),
];
await em.persistAndFlush(outputRule);
- Observe DB result:
{"constant": "{\"value\":\42,\"type\":\"number\"}", "calculation_type": "constant"}
[{"operator": "eq", "comparison_value": "foo"},{"operator": "gt", "comparison_value": "2024-01-01T00:00:00Z'"}]-> comparisonValue should be wrapped in custom type json instead plain - Get it from DB without query filter: constant gets parsed properly, conditions.comparisonValue not (as it is already saved wrong)
- Get it from DB with query filter:
{"$and":[{"calculation":{"constant":{"$eq":"42"}}}]}-> Should match but it doesn't
What driver are you using?
@mikro-orm/postgresql
MikroORM version
6.4.13
Node.js version
24.0.1
Operating system
Debian
Validations
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord.
- The provided reproduction is a minimal reproducible example of the bug.
Metadata
Metadata
Assignees
Labels
No labels