Skip to content

Commit

Permalink
types: add JSONSerialized helper that can convert HydratedDocument to…
Browse files Browse the repository at this point in the history
… JSON output type

Fix #14451
  • Loading branch information
vkarpov15 committed Oct 23, 2024
1 parent 925327f commit ff24d86
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 1 deletion.
2 changes: 1 addition & 1 deletion test/types/check-types-filename.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const checkFolder = (folder) => {
}
continue;
} else {
console.error('File ' + entry + ' is not having a valid file-extension.\n');
console.error('File ' + entry + ' does not have a valid extension, must be .d.ts or .gitignore.\n');
process.exit(1);
}
}
Expand Down
26 changes: 26 additions & 0 deletions test/types/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
InferRawDocType,
InferSchemaType,
InsertManyOptions,
JSONSerialized,
ObtainDocumentType,
ObtainSchemaGeneric,
ResolveSchemaOptions,
Expand Down Expand Up @@ -1681,3 +1682,28 @@ async function gh14902() {
expectType<Binary | null | undefined>(doc.image);
expectType<Binary | null | undefined>(doc.subdoc!.testBuf);
}

async function gh14451() {
const exampleSchema = new Schema({
myId: { type: 'ObjectId' },
myRequiredId: { type: 'ObjectId', required: true },
subdoc: {
type: new Schema({
subdocProp: Date
})
}
// docArr: [{ nums: [Number], times: [Date] }]
});

const Test = model('Test', exampleSchema);

type TestJSON = JSONSerialized<InferSchemaType<typeof exampleSchema>>;
expectType<{
myId?: string | undefined | null,
myRequiredId: string,
subdoc?: {
subdocProp?: string | undefined | null
} | null,
// docArr: { nums: number[], times: string[] }[]
}>({} as TestJSON);
}
44 changes: 44 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,50 @@ declare module 'mongoose' {
: BufferToBinary<T[K]>;
} : T;

export type ObjectIdToString<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
[K in keyof T]: T[K] extends mongodb.ObjectId
? string
: T[K] extends (mongodb.ObjectId | null | undefined)
? string | null | undefined
: T[K] extends Types.DocumentArray<infer ItemType>
? Types.DocumentArray<ObjectIdToString<ItemType>>
: T[K] extends Types.Subdocument<unknown, unknown, infer SubdocType>
? HydratedSingleSubdocument<ObjectIdToString<SubdocType>>
: ObjectIdToString<T[K]>;
} : T;

export type DateToString<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
[K in keyof T]: T[K] extends NativeDate
? string
: T[K] extends (NativeDate | null | undefined)
? string | null | undefined
: T[K] extends Types.DocumentArray<infer ItemType>
? Types.DocumentArray<DateToString<ItemType>>
: T[K] extends Types.Subdocument<unknown, unknown, infer SubdocType>
? HydratedSingleSubdocument<DateToString<SubdocType>>
: DateToString<T[K]>;
} : T;

export type SubdocsToPOJOs<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
[K in keyof T]: T[K] extends NativeDate
? string
: T[K] extends (NativeDate | null | undefined)
? string | null | undefined
: T[K] extends Types.DocumentArray<infer ItemType>
? ItemType
: T[K] extends Types.Subdocument<unknown, unknown, infer SubdocType>
? SubdocType
: SubdocsToPOJOs<T[K]>;
} : T;

export type JSONSerialized<T> = SubdocsToPOJOs<
FlattenMaps<
ObjectIdToString<
DateToString<T>
>
>
>;

/**
* Separate type is needed for properties of union type (for example, Types.DocumentArray | undefined) to apply conditional check to each member of it
* https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
Expand Down

0 comments on commit ff24d86

Please sign in to comment.