Skip to content

Commit 6ab97f2

Browse files
authored
Merge pull request Automattic#14981 from Automattic/vkarpov15/Automatticgh-14451
types: add JSONSerialized helper that can convert HydratedDocument to JSON output type
2 parents f2b2816 + 858cc0a commit 6ab97f2

File tree

3 files changed

+107
-1
lines changed

3 files changed

+107
-1
lines changed

test/types/check-types-filename.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const checkFolder = (folder) => {
1818
}
1919
continue;
2020
} else {
21-
console.error('File ' + entry + ' is not having a valid file-extension.\n');
21+
console.error('File ' + entry + ' does not have a valid extension, must be .d.ts or .gitignore.\n');
2222
process.exit(1);
2323
}
2424
}

test/types/schema.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
InferRawDocType,
1111
InferSchemaType,
1212
InsertManyOptions,
13+
JSONSerialized,
1314
ObtainDocumentType,
1415
ObtainSchemaGeneric,
1516
ResolveSchemaOptions,
@@ -1681,3 +1682,35 @@ async function gh14902() {
16811682
expectType<Binary | null | undefined>(doc.image);
16821683
expectType<Binary | null | undefined>(doc.subdoc!.testBuf);
16831684
}
1685+
1686+
async function gh14451() {
1687+
const exampleSchema = new Schema({
1688+
myId: { type: 'ObjectId' },
1689+
myRequiredId: { type: 'ObjectId', required: true },
1690+
myBuf: { type: Buffer, required: true },
1691+
subdoc: {
1692+
type: new Schema({
1693+
subdocProp: Date
1694+
})
1695+
},
1696+
docArr: [{ nums: [Number], times: [{ type: Date }] }],
1697+
myMap: {
1698+
type: Map,
1699+
of: String
1700+
}
1701+
});
1702+
1703+
const Test = model('Test', exampleSchema);
1704+
1705+
type TestJSON = JSONSerialized<InferSchemaType<typeof exampleSchema>>;
1706+
expectType<{
1707+
myId?: string | undefined | null,
1708+
myRequiredId: string,
1709+
myBuf: { type: 'buffer', data: number[] },
1710+
subdoc?: {
1711+
subdocProp?: string | undefined | null
1712+
} | null,
1713+
docArr: { nums: number[], times: string[] }[],
1714+
myMap?: Record<string, string> | null | undefined
1715+
}>({} as TestJSON);
1716+
}

types/index.d.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,9 @@ declare module 'mongoose' {
706706
[K in keyof T]: FlattenProperty<T[K]>;
707707
};
708708

709+
/**
710+
* Converts any Buffer properties into mongodb.Binary instances, which is what `lean()` returns
711+
*/
709712
export type BufferToBinary<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
710713
[K in keyof T]: T[K] extends Buffer
711714
? mongodb.Binary
@@ -718,6 +721,76 @@ declare module 'mongoose' {
718721
: BufferToBinary<T[K]>;
719722
} : T;
720723

724+
/**
725+
* Converts any Buffer properties into { type: 'buffer', data: [1, 2, 3] } format for JSON serialization
726+
*/
727+
export type BufferToJSON<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
728+
[K in keyof T]: T[K] extends Buffer
729+
? { type: 'buffer', data: number[] }
730+
: T[K] extends (Buffer | null | undefined)
731+
? { type: 'buffer', data: number[] } | null | undefined
732+
: T[K] extends Types.DocumentArray<infer ItemType>
733+
? Types.DocumentArray<BufferToBinary<ItemType>>
734+
: T[K] extends Types.Subdocument<unknown, unknown, infer SubdocType>
735+
? HydratedSingleSubdocument<SubdocType>
736+
: BufferToBinary<T[K]>;
737+
} : T;
738+
739+
/**
740+
* Converts any ObjectId properties into strings for JSON serialization
741+
*/
742+
export type ObjectIdToString<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
743+
[K in keyof T]: T[K] extends mongodb.ObjectId
744+
? string
745+
: T[K] extends (mongodb.ObjectId | null | undefined)
746+
? string | null | undefined
747+
: T[K] extends Types.DocumentArray<infer ItemType>
748+
? Types.DocumentArray<ObjectIdToString<ItemType>>
749+
: T[K] extends Types.Subdocument<unknown, unknown, infer SubdocType>
750+
? HydratedSingleSubdocument<ObjectIdToString<SubdocType>>
751+
: ObjectIdToString<T[K]>;
752+
} : T;
753+
754+
/**
755+
* Converts any Date properties into strings for JSON serialization
756+
*/
757+
export type DateToString<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
758+
[K in keyof T]: T[K] extends NativeDate
759+
? string
760+
: T[K] extends (NativeDate | null | undefined)
761+
? string | null | undefined
762+
: T[K] extends Types.DocumentArray<infer ItemType>
763+
? Types.DocumentArray<DateToString<ItemType>>
764+
: T[K] extends Types.Subdocument<unknown, unknown, infer SubdocType>
765+
? HydratedSingleSubdocument<DateToString<SubdocType>>
766+
: DateToString<T[K]>;
767+
} : T;
768+
769+
/**
770+
* Converts any Mongoose subdocuments (single nested or doc arrays) into POJO equivalents
771+
*/
772+
export type SubdocsToPOJOs<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
773+
[K in keyof T]: T[K] extends NativeDate
774+
? string
775+
: T[K] extends (NativeDate | null | undefined)
776+
? string | null | undefined
777+
: T[K] extends Types.DocumentArray<infer ItemType>
778+
? ItemType[]
779+
: T[K] extends Types.Subdocument<unknown, unknown, infer SubdocType>
780+
? SubdocType
781+
: SubdocsToPOJOs<T[K]>;
782+
} : T;
783+
784+
export type JSONSerialized<T> = SubdocsToPOJOs<
785+
FlattenMaps<
786+
BufferToJSON<
787+
ObjectIdToString<
788+
DateToString<T>
789+
>
790+
>
791+
>
792+
>;
793+
721794
/**
722795
* Separate type is needed for properties of union type (for example, Types.DocumentArray | undefined) to apply conditional check to each member of it
723796
* https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types

0 commit comments

Comments
 (0)