-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
deserializeHelpers.ts
136 lines (127 loc) · 4.51 KB
/
deserializeHelpers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { Headers } from '@aws-amplify/core/internals/aws-client-utils';
type PropertyNameWithStringValue = string;
type PropertyNameWithSubsequentDeserializer<T> = [string, (any) => T];
type Instruction<T> =
| PropertyNameWithStringValue
| PropertyNameWithSubsequentDeserializer<T>;
type InferInstructionResultType<T extends Instruction<any>> =
| (T extends PropertyNameWithSubsequentDeserializer<infer R> ? R : string)
| undefined;
/**
* Maps an object to a new object using the provided instructions.
* The instructions are a map of the returning mapped object's property names to a single instruction of how to map the
* value from the original object to the new object. There are two types of instructions:
*
* 1. A string representing the property name of the original object to map to the new object. The value mapped from
* the original object will be the same as the value in the new object, and it can ONLY be string.
*
* 2. An array of two elements. The first element is the property name of the original object to map to the new object.
* The second element is a function that takes the value from the original object and returns the value to be mapped to
* the new object. The function can return any type.
*
* Example:
* ```typescript
* const input = {
* Foo: 'foo',
* BarList: [{value: 'bar1'}, {value: 'bar2'}]
* }
* const output = map(input, {
* someFoo: 'Foo',
* bar: ['BarList', (barList) => barList.map(bar => bar.value)]
* baz: 'Baz' // Baz does not exist in input, so it will not be in the output.
* });
* // output = { someFoo: 'foo', bar: ['bar1', 'bar2'] }
* ```
*
* @param obj The object containing the data to compose mapped object.
* @param instructions The instructions mapping the object values to the new object.
* @returns A new object with the mapped values.
*
* @internal
*/
export const map = <Instructions extends { [key: string]: Instruction<any> }>(
obj: Record<string, any>,
instructions: Instructions
): {
[K in keyof Instructions]: InferInstructionResultType<Instructions[K]>;
} => {
const result = {} as Record<keyof Instructions, any>;
for (const [key, instruction] of Object.entries(instructions)) {
const [accessor, deserializer] = Array.isArray(instruction)
? instruction
: [instruction];
if (obj.hasOwnProperty(accessor)) {
result[key as keyof Instructions] = deserializer
? deserializer(obj[accessor])
: String(obj[accessor]);
}
}
return result;
};
/**
* Deserializes a string to a number. Returns undefined if input is undefined.
*
* @internal
*/
export const deserializeNumber = (value?: string): number => {
return value ? Number(value) : undefined;
};
/**
* Deserializes a string to a boolean. Returns undefined if input is undefined. Returns true if input is 'true',
* otherwise false.
*
* @internal
*/
export const deserializeBoolean = (value?: string): boolean | undefined => {
return value ? value === 'true' : undefined;
};
/**
* Deserializes a string to a Date. Returns undefined if input is undefined.
* It supports epoch timestamp; rfc3339(cannot have a UTC, fractional precision supported); rfc7231(section 7.1.1.1)
*
* @see https://www.epoch101.com/
* @see https://datatracker.ietf.org/doc/html/rfc3339.html#section-5.6
* @see https://datatracker.ietf.org/doc/html/rfc7231.html#section-7.1.1.1
*
* @note For bundle size consideration, we use Date constructor to parse the timestamp string. There might be slight
* difference among browsers.
*
* @internal
*/
export const deserializeTimestamp = (value: string): Date | undefined => {
return value ? new Date(value) : undefined;
};
/**
* Function that makes sure the deserializer receives non-empty array.
*
* @internal
*/
export const emptyArrayGuard = <T extends Array<any>>(
value: any,
deserializer: (value: any[]) => T
): T => {
if (value === '') {
return [] as T;
}
const valueArray = (Array.isArray(value) ? value : [value]).filter(
e => e != null
);
return deserializer(valueArray);
};
/**
* @internal
*/
export const deserializeMetadata = (
headers: Headers
): Record<string, string> => {
const objectMetadataHeaderPrefix = 'x-amz-meta-';
const deserialized = Object.keys(headers)
.filter(header => header.startsWith(objectMetadataHeaderPrefix))
.reduce((acc, header) => {
acc[header.replace(objectMetadataHeaderPrefix, '')] = headers[header];
return acc;
}, {} as any);
return Object.keys(deserialized).length > 0 ? deserialized : undefined;
};