UNPKG

34.6 kBPlain TextView Raw
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License. See License.txt in the project root for license information.
3
4import * as base64 from "./util/base64";
5import * as utils from "./util/utils";
6
7export class Serializer {
8 constructor(
9 public readonly modelMappers: { [key: string]: any } = {},
10 public readonly isXML?: boolean
11 ) {}
12
13 validateConstraints(mapper: Mapper, value: any, objectName: string): void {
14 const failValidation = (constraintName: keyof MapperConstraints, constraintValue: any) => {
15 throw new Error(
16 `"${objectName}" with value "${value}" should satisfy the constraint "${constraintName}": ${constraintValue}.`
17 );
18 };
19 if (mapper.constraints && value != undefined) {
20 const {
21 ExclusiveMaximum,
22 ExclusiveMinimum,
23 InclusiveMaximum,
24 InclusiveMinimum,
25 MaxItems,
26 MaxLength,
27 MinItems,
28 MinLength,
29 MultipleOf,
30 Pattern,
31 UniqueItems,
32 } = mapper.constraints;
33 if (ExclusiveMaximum != undefined && value >= ExclusiveMaximum) {
34 failValidation("ExclusiveMaximum", ExclusiveMaximum);
35 }
36 if (ExclusiveMinimum != undefined && value <= ExclusiveMinimum) {
37 failValidation("ExclusiveMinimum", ExclusiveMinimum);
38 }
39 if (InclusiveMaximum != undefined && value > InclusiveMaximum) {
40 failValidation("InclusiveMaximum", InclusiveMaximum);
41 }
42 if (InclusiveMinimum != undefined && value < InclusiveMinimum) {
43 failValidation("InclusiveMinimum", InclusiveMinimum);
44 }
45 if (MaxItems != undefined && value.length > MaxItems) {
46 failValidation("MaxItems", MaxItems);
47 }
48 if (MaxLength != undefined && value.length > MaxLength) {
49 failValidation("MaxLength", MaxLength);
50 }
51 if (MinItems != undefined && value.length < MinItems) {
52 failValidation("MinItems", MinItems);
53 }
54 if (MinLength != undefined && value.length < MinLength) {
55 failValidation("MinLength", MinLength);
56 }
57 if (MultipleOf != undefined && value % MultipleOf !== 0) {
58 failValidation("MultipleOf", MultipleOf);
59 }
60 if (Pattern) {
61 const pattern: RegExp = typeof Pattern === "string" ? new RegExp(Pattern) : Pattern;
62 if (typeof value !== "string" || value.match(pattern) === null) {
63 failValidation("Pattern", Pattern);
64 }
65 }
66 if (
67 UniqueItems &&
68 value.some((item: any, i: number, ar: Array<any>) => ar.indexOf(item) !== i)
69 ) {
70 failValidation("UniqueItems", UniqueItems);
71 }
72 }
73 }
74
75 /**
76 * Serialize the given object based on its metadata defined in the mapper
77 *
78 * @param {Mapper} mapper The mapper which defines the metadata of the serializable object
79 *
80 * @param {object|string|Array|number|boolean|Date|stream} object A valid Javascript object to be serialized
81 *
82 * @param {string} objectName Name of the serialized object
83 *
84 * @returns {object|string|Array|number|boolean|Date|stream} A valid serialized Javascript object
85 */
86 serialize(mapper: Mapper, object: any, objectName?: string): any {
87 let payload: any = {};
88 const mapperType = mapper.type.name as string;
89 if (!objectName) {
90 objectName = mapper.serializedName!;
91 }
92 if (mapperType.match(/^Sequence$/gi) !== null) {
93 payload = [];
94 }
95
96 if (mapper.isConstant) {
97 object = mapper.defaultValue;
98 }
99
100 // This table of allowed values should help explain
101 // the mapper.required and mapper.nullable properties.
102 // X means "neither undefined or null are allowed".
103 // || required
104 // || true | false
105 // nullable || ==========================
106 // true || null | undefined/null
107 // false || X | undefined
108 // undefined || X | undefined/null
109
110 const { required, nullable } = mapper;
111
112 if (required && nullable && object === undefined) {
113 throw new Error(`${objectName} cannot be undefined.`);
114 }
115 if (required && !nullable && object == undefined) {
116 throw new Error(`${objectName} cannot be null or undefined.`);
117 }
118 if (!required && nullable === false && object === null) {
119 throw new Error(`${objectName} cannot be null.`);
120 }
121
122 if (object == undefined) {
123 payload = object;
124 } else {
125 // Validate Constraints if any
126 this.validateConstraints(mapper, object, objectName);
127 if (mapperType.match(/^any$/gi) !== null) {
128 payload = object;
129 } else if (mapperType.match(/^(Number|String|Boolean|Object|Stream|Uuid)$/gi) !== null) {
130 payload = serializeBasicTypes(mapperType, objectName, object);
131 } else if (mapperType.match(/^Enum$/gi) !== null) {
132 const enumMapper: EnumMapper = mapper as EnumMapper;
133 payload = serializeEnumType(objectName, enumMapper.type.allowedValues, object);
134 } else if (
135 mapperType.match(/^(Date|DateTime|TimeSpan|DateTimeRfc1123|UnixTime)$/gi) !== null
136 ) {
137 payload = serializeDateTypes(mapperType, object, objectName);
138 } else if (mapperType.match(/^ByteArray$/gi) !== null) {
139 payload = serializeByteArrayType(objectName, object);
140 } else if (mapperType.match(/^Base64Url$/gi) !== null) {
141 payload = serializeBase64UrlType(objectName, object);
142 } else if (mapperType.match(/^Sequence$/gi) !== null) {
143 payload = serializeSequenceType(this, mapper as SequenceMapper, object, objectName);
144 } else if (mapperType.match(/^Dictionary$/gi) !== null) {
145 payload = serializeDictionaryType(this, mapper as DictionaryMapper, object, objectName);
146 } else if (mapperType.match(/^Composite$/gi) !== null) {
147 payload = serializeCompositeType(this, mapper as CompositeMapper, object, objectName);
148 }
149 }
150 return payload;
151 }
152
153 /**
154 * Deserialize the given object based on its metadata defined in the mapper
155 *
156 * @param {object} mapper The mapper which defines the metadata of the serializable object
157 *
158 * @param {object|string|Array|number|boolean|Date|stream} responseBody A valid Javascript entity to be deserialized
159 *
160 * @param {string} objectName Name of the deserialized object
161 *
162 * @returns {object|string|Array|number|boolean|Date|stream} A valid deserialized Javascript object
163 */
164 deserialize(mapper: Mapper, responseBody: any, objectName: string): any {
165 if (responseBody == undefined) {
166 if (this.isXML && mapper.type.name === "Sequence" && !mapper.xmlIsWrapped) {
167 // Edge case for empty XML non-wrapped lists. xml2js can't distinguish
168 // between the list being empty versus being missing,
169 // so let's do the more user-friendly thing and return an empty list.
170 responseBody = [];
171 }
172 // specifically check for undefined as default value can be a falsey value `0, "", false, null`
173 if (mapper.defaultValue !== undefined) {
174 responseBody = mapper.defaultValue;
175 }
176 return responseBody;
177 }
178
179 let payload: any;
180 const mapperType = mapper.type.name;
181 if (!objectName) {
182 objectName = mapper.serializedName!;
183 }
184
185 if (mapperType.match(/^Composite$/gi) !== null) {
186 payload = deserializeCompositeType(this, mapper as CompositeMapper, responseBody, objectName);
187 } else {
188 if (this.isXML) {
189 /**
190 * If the mapper specifies this as a non-composite type value but the responseBody contains
191 * both header ("$") and body ("_") properties, then just reduce the responseBody value to
192 * the body ("_") property.
193 */
194 if (responseBody["$"] != undefined && responseBody["_"] != undefined) {
195 responseBody = responseBody["_"];
196 }
197 }
198
199 if (mapperType.match(/^Number$/gi) !== null) {
200 payload = parseFloat(responseBody);
201 if (isNaN(payload)) {
202 payload = responseBody;
203 }
204 } else if (mapperType.match(/^Boolean$/gi) !== null) {
205 if (responseBody === "true") {
206 payload = true;
207 } else if (responseBody === "false") {
208 payload = false;
209 } else {
210 payload = responseBody;
211 }
212 } else if (mapperType.match(/^(String|Enum|Object|Stream|Uuid|TimeSpan|any)$/gi) !== null) {
213 payload = responseBody;
214 } else if (mapperType.match(/^(Date|DateTime|DateTimeRfc1123)$/gi) !== null) {
215 payload = new Date(responseBody);
216 } else if (mapperType.match(/^UnixTime$/gi) !== null) {
217 payload = unixTimeToDate(responseBody);
218 } else if (mapperType.match(/^ByteArray$/gi) !== null) {
219 payload = base64.decodeString(responseBody);
220 } else if (mapperType.match(/^Base64Url$/gi) !== null) {
221 payload = base64UrlToByteArray(responseBody);
222 } else if (mapperType.match(/^Sequence$/gi) !== null) {
223 payload = deserializeSequenceType(this, mapper as SequenceMapper, responseBody, objectName);
224 } else if (mapperType.match(/^Dictionary$/gi) !== null) {
225 payload = deserializeDictionaryType(
226 this,
227 mapper as DictionaryMapper,
228 responseBody,
229 objectName
230 );
231 }
232 }
233
234 if (mapper.isConstant) {
235 payload = mapper.defaultValue;
236 }
237
238 return payload;
239 }
240}
241
242function trimEnd(str: string, ch: string) {
243 let len = str.length;
244 while (len - 1 >= 0 && str[len - 1] === ch) {
245 --len;
246 }
247 return str.substr(0, len);
248}
249
250function bufferToBase64Url(buffer: any): string | undefined {
251 if (!buffer) {
252 return undefined;
253 }
254 if (!(buffer instanceof Uint8Array)) {
255 throw new Error(`Please provide an input of type Uint8Array for converting to Base64Url.`);
256 }
257 // Uint8Array to Base64.
258 const str = base64.encodeByteArray(buffer);
259 // Base64 to Base64Url.
260 return trimEnd(str, "=").replace(/\+/g, "-").replace(/\//g, "_");
261}
262
263function base64UrlToByteArray(str: string): Uint8Array | undefined {
264 if (!str) {
265 return undefined;
266 }
267 if (str && typeof str.valueOf() !== "string") {
268 throw new Error("Please provide an input of type string for converting to Uint8Array");
269 }
270 // Base64Url to Base64.
271 str = str.replace(/\-/g, "+").replace(/\_/g, "/");
272 // Base64 to Uint8Array.
273 return base64.decodeString(str);
274}
275
276function splitSerializeName(prop: string | undefined): string[] {
277 const classes: string[] = [];
278 let partialclass = "";
279 if (prop) {
280 const subwords = prop.split(".");
281
282 for (const item of subwords) {
283 if (item.charAt(item.length - 1) === "\\") {
284 partialclass += item.substr(0, item.length - 1) + ".";
285 } else {
286 partialclass += item;
287 classes.push(partialclass);
288 partialclass = "";
289 }
290 }
291 }
292
293 return classes;
294}
295
296function dateToUnixTime(d: string | Date): number | undefined {
297 if (!d) {
298 return undefined;
299 }
300
301 if (typeof d.valueOf() === "string") {
302 d = new Date(d as string);
303 }
304 return Math.floor((d as Date).getTime() / 1000);
305}
306
307function unixTimeToDate(n: number): Date | undefined {
308 if (!n) {
309 return undefined;
310 }
311 return new Date(n * 1000);
312}
313
314function serializeBasicTypes(typeName: string, objectName: string, value: any): any {
315 if (value !== null && value !== undefined) {
316 if (typeName.match(/^Number$/gi) !== null) {
317 if (typeof value !== "number") {
318 throw new Error(`${objectName} with value ${value} must be of type number.`);
319 }
320 } else if (typeName.match(/^String$/gi) !== null) {
321 if (typeof value.valueOf() !== "string") {
322 throw new Error(`${objectName} with value "${value}" must be of type string.`);
323 }
324 } else if (typeName.match(/^Uuid$/gi) !== null) {
325 if (!(typeof value.valueOf() === "string" && utils.isValidUuid(value))) {
326 throw new Error(
327 `${objectName} with value "${value}" must be of type string and a valid uuid.`
328 );
329 }
330 } else if (typeName.match(/^Boolean$/gi) !== null) {
331 if (typeof value !== "boolean") {
332 throw new Error(`${objectName} with value ${value} must be of type boolean.`);
333 }
334 } else if (typeName.match(/^Stream$/gi) !== null) {
335 const objectType = typeof value;
336 if (
337 objectType !== "string" &&
338 objectType !== "function" &&
339 !(value instanceof ArrayBuffer) &&
340 !ArrayBuffer.isView(value) &&
341 !(typeof Blob === "function" && value instanceof Blob)
342 ) {
343 throw new Error(
344 `${objectName} must be a string, Blob, ArrayBuffer, ArrayBufferView, or a function returning NodeJS.ReadableStream.`
345 );
346 }
347 }
348 }
349 return value;
350}
351
352function serializeEnumType(objectName: string, allowedValues: Array<any>, value: any): any {
353 if (!allowedValues) {
354 throw new Error(
355 `Please provide a set of allowedValues to validate ${objectName} as an Enum Type.`
356 );
357 }
358 const isPresent = allowedValues.some((item) => {
359 if (typeof item.valueOf() === "string") {
360 return item.toLowerCase() === value.toLowerCase();
361 }
362 return item === value;
363 });
364 if (!isPresent) {
365 throw new Error(
366 `${value} is not a valid value for ${objectName}. The valid values are: ${JSON.stringify(
367 allowedValues
368 )}.`
369 );
370 }
371 return value;
372}
373
374function serializeByteArrayType(objectName: string, value: any): any {
375 if (value != undefined) {
376 if (!(value instanceof Uint8Array)) {
377 throw new Error(`${objectName} must be of type Uint8Array.`);
378 }
379 value = base64.encodeByteArray(value);
380 }
381 return value;
382}
383
384function serializeBase64UrlType(objectName: string, value: any): any {
385 if (value != undefined) {
386 if (!(value instanceof Uint8Array)) {
387 throw new Error(`${objectName} must be of type Uint8Array.`);
388 }
389 value = bufferToBase64Url(value);
390 }
391 return value;
392}
393
394function serializeDateTypes(typeName: string, value: any, objectName: string) {
395 if (value != undefined) {
396 if (typeName.match(/^Date$/gi) !== null) {
397 if (
398 !(
399 value instanceof Date ||
400 (typeof value.valueOf() === "string" && !isNaN(Date.parse(value)))
401 )
402 ) {
403 throw new Error(`${objectName} must be an instanceof Date or a string in ISO8601 format.`);
404 }
405 value =
406 value instanceof Date
407 ? value.toISOString().substring(0, 10)
408 : new Date(value).toISOString().substring(0, 10);
409 } else if (typeName.match(/^DateTime$/gi) !== null) {
410 if (
411 !(
412 value instanceof Date ||
413 (typeof value.valueOf() === "string" && !isNaN(Date.parse(value)))
414 )
415 ) {
416 throw new Error(`${objectName} must be an instanceof Date or a string in ISO8601 format.`);
417 }
418 value = value instanceof Date ? value.toISOString() : new Date(value).toISOString();
419 } else if (typeName.match(/^DateTimeRfc1123$/gi) !== null) {
420 if (
421 !(
422 value instanceof Date ||
423 (typeof value.valueOf() === "string" && !isNaN(Date.parse(value)))
424 )
425 ) {
426 throw new Error(`${objectName} must be an instanceof Date or a string in RFC-1123 format.`);
427 }
428 value = value instanceof Date ? value.toUTCString() : new Date(value).toUTCString();
429 } else if (typeName.match(/^UnixTime$/gi) !== null) {
430 if (
431 !(
432 value instanceof Date ||
433 (typeof value.valueOf() === "string" && !isNaN(Date.parse(value)))
434 )
435 ) {
436 throw new Error(
437 `${objectName} must be an instanceof Date or a string in RFC-1123/ISO8601 format ` +
438 `for it to be serialized in UnixTime/Epoch format.`
439 );
440 }
441 value = dateToUnixTime(value);
442 } else if (typeName.match(/^TimeSpan$/gi) !== null) {
443 if (!utils.isDuration(value)) {
444 throw new Error(
445 `${objectName} must be a string in ISO 8601 format. Instead was "${value}".`
446 );
447 }
448 value = value;
449 }
450 }
451 return value;
452}
453
454function serializeSequenceType(
455 serializer: Serializer,
456 mapper: SequenceMapper,
457 object: any,
458 objectName: string
459) {
460 if (!Array.isArray(object)) {
461 throw new Error(`${objectName} must be of type Array.`);
462 }
463 const elementType = mapper.type.element;
464 if (!elementType || typeof elementType !== "object") {
465 throw new Error(
466 `element" metadata for an Array must be defined in the ` +
467 `mapper and it must of type "object" in ${objectName}.`
468 );
469 }
470 const tempArray = [];
471 for (let i = 0; i < object.length; i++) {
472 tempArray[i] = serializer.serialize(elementType, object[i], objectName);
473 }
474 return tempArray;
475}
476
477function serializeDictionaryType(
478 serializer: Serializer,
479 mapper: DictionaryMapper,
480 object: any,
481 objectName: string
482) {
483 if (typeof object !== "object") {
484 throw new Error(`${objectName} must be of type object.`);
485 }
486 const valueType = mapper.type.value;
487 if (!valueType || typeof valueType !== "object") {
488 throw new Error(
489 `"value" metadata for a Dictionary must be defined in the ` +
490 `mapper and it must of type "object" in ${objectName}.`
491 );
492 }
493 const tempDictionary: { [key: string]: any } = {};
494 for (const key of Object.keys(object)) {
495 tempDictionary[key] = serializer.serialize(valueType, object[key], objectName + "." + key);
496 }
497 return tempDictionary;
498}
499
500/**
501 * Resolves a composite mapper's modelProperties.
502 * @param serializer the serializer containing the entire set of mappers
503 * @param mapper the composite mapper to resolve
504 */
505function resolveModelProperties(
506 serializer: Serializer,
507 mapper: CompositeMapper,
508 objectName: string
509): { [propertyName: string]: Mapper } {
510 let modelProps = mapper.type.modelProperties;
511 if (!modelProps) {
512 const className = mapper.type.className;
513 if (!className) {
514 throw new Error(
515 `Class name for model "${objectName}" is not provided in the mapper "${JSON.stringify(
516 mapper,
517 undefined,
518 2
519 )}".`
520 );
521 }
522
523 const modelMapper = serializer.modelMappers[className];
524 if (!modelMapper) {
525 throw new Error(`mapper() cannot be null or undefined for model "${className}".`);
526 }
527 modelProps = modelMapper.type.modelProperties;
528 if (!modelProps) {
529 throw new Error(
530 `modelProperties cannot be null or undefined in the ` +
531 `mapper "${JSON.stringify(
532 modelMapper
533 )}" of type "${className}" for object "${objectName}".`
534 );
535 }
536 }
537
538 return modelProps;
539}
540
541function serializeCompositeType(
542 serializer: Serializer,
543 mapper: CompositeMapper,
544 object: any,
545 objectName: string
546) {
547 if (getPolymorphicDiscriminatorRecursively(serializer, mapper)) {
548 mapper = getPolymorphicMapper(serializer, mapper, object, "clientName");
549 }
550
551 if (object != undefined) {
552 const payload: any = {};
553 const modelProps = resolveModelProperties(serializer, mapper, objectName);
554 for (const key of Object.keys(modelProps)) {
555 const propertyMapper = modelProps[key];
556 if (propertyMapper.readOnly) {
557 continue;
558 }
559
560 let propName: string | undefined;
561 let parentObject: any = payload;
562 if (serializer.isXML) {
563 if (propertyMapper.xmlIsWrapped) {
564 propName = propertyMapper.xmlName;
565 } else {
566 propName = propertyMapper.xmlElementName || propertyMapper.xmlName;
567 }
568 } else {
569 const paths = splitSerializeName(propertyMapper.serializedName!);
570 propName = paths.pop();
571
572 for (const pathName of paths) {
573 const childObject = parentObject[pathName];
574 if (childObject == undefined && object[key] != undefined) {
575 parentObject[pathName] = {};
576 }
577 parentObject = parentObject[pathName];
578 }
579 }
580
581 if (parentObject != undefined) {
582 const propertyObjectName =
583 propertyMapper.serializedName !== ""
584 ? objectName + "." + propertyMapper.serializedName
585 : objectName;
586
587 let toSerialize = object[key];
588 const polymorphicDiscriminator = getPolymorphicDiscriminatorRecursively(serializer, mapper);
589 if (
590 polymorphicDiscriminator &&
591 polymorphicDiscriminator.clientName === key &&
592 toSerialize == undefined
593 ) {
594 toSerialize = mapper.serializedName;
595 }
596
597 const serializedValue = serializer.serialize(
598 propertyMapper,
599 toSerialize,
600 propertyObjectName
601 );
602 if (serializedValue !== undefined && propName != undefined) {
603 if (propertyMapper.xmlIsAttribute) {
604 // $ is the key attributes are kept under in xml2js.
605 // This keeps things simple while preventing name collision
606 // with names in user documents.
607 parentObject.$ = parentObject.$ || {};
608 parentObject.$[propName] = serializedValue;
609 } else if (propertyMapper.xmlIsWrapped) {
610 parentObject[propName] = { [propertyMapper.xmlElementName!]: serializedValue };
611 } else {
612 parentObject[propName] = serializedValue;
613 }
614 }
615 }
616 }
617
618 const additionalPropertiesMapper = mapper.type.additionalProperties;
619 if (additionalPropertiesMapper) {
620 const propNames = Object.keys(modelProps);
621 for (const clientPropName in object) {
622 const isAdditionalProperty = propNames.every((pn) => pn !== clientPropName);
623 if (isAdditionalProperty) {
624 payload[clientPropName] = serializer.serialize(
625 additionalPropertiesMapper,
626 object[clientPropName],
627 objectName + '["' + clientPropName + '"]'
628 );
629 }
630 }
631 }
632
633 return payload;
634 }
635 return object;
636}
637
638function isSpecialXmlProperty(propertyName: string): boolean {
639 return ["$", "_"].includes(propertyName);
640}
641
642function deserializeCompositeType(
643 serializer: Serializer,
644 mapper: CompositeMapper,
645 responseBody: any,
646 objectName: string
647): any {
648 if (getPolymorphicDiscriminatorRecursively(serializer, mapper)) {
649 mapper = getPolymorphicMapper(serializer, mapper, responseBody, "serializedName");
650 }
651
652 const modelProps = resolveModelProperties(serializer, mapper, objectName);
653 let instance: { [key: string]: any } = {};
654 const handledPropertyNames: string[] = [];
655
656 for (const key of Object.keys(modelProps)) {
657 const propertyMapper = modelProps[key];
658 const paths = splitSerializeName(modelProps[key].serializedName!);
659 handledPropertyNames.push(paths[0]);
660 const { serializedName, xmlName, xmlElementName } = propertyMapper;
661 let propertyObjectName = objectName;
662 if (serializedName !== "" && serializedName !== undefined) {
663 propertyObjectName = objectName + "." + serializedName;
664 }
665
666 const headerCollectionPrefix = (propertyMapper as DictionaryMapper).headerCollectionPrefix;
667 if (headerCollectionPrefix) {
668 const dictionary: any = {};
669 for (const headerKey of Object.keys(responseBody)) {
670 if (headerKey.startsWith(headerCollectionPrefix)) {
671 dictionary[headerKey.substring(headerCollectionPrefix.length)] = serializer.deserialize(
672 (propertyMapper as DictionaryMapper).type.value,
673 responseBody[headerKey],
674 propertyObjectName
675 );
676 }
677
678 handledPropertyNames.push(headerKey);
679 }
680 instance[key] = dictionary;
681 } else if (serializer.isXML) {
682 if (propertyMapper.xmlIsAttribute && responseBody.$) {
683 instance[key] = serializer.deserialize(
684 propertyMapper,
685 responseBody.$[xmlName!],
686 propertyObjectName
687 );
688 } else {
689 const propertyName = xmlElementName || xmlName || serializedName;
690 let unwrappedProperty = responseBody[propertyName!];
691 if (propertyMapper.xmlIsWrapped) {
692 unwrappedProperty = responseBody[xmlName!];
693 unwrappedProperty = unwrappedProperty && unwrappedProperty[xmlElementName!];
694
695 const isEmptyWrappedList = unwrappedProperty === undefined;
696 if (isEmptyWrappedList) {
697 unwrappedProperty = [];
698 }
699 }
700 instance[key] = serializer.deserialize(
701 propertyMapper,
702 unwrappedProperty,
703 propertyObjectName
704 );
705 }
706 } else {
707 // deserialize the property if it is present in the provided responseBody instance
708 let propertyInstance;
709 let res = responseBody;
710 // traversing the object step by step.
711 for (const item of paths) {
712 if (!res) break;
713 res = res[item];
714 }
715 propertyInstance = res;
716 const polymorphicDiscriminator = mapper.type.polymorphicDiscriminator;
717 // checking that the model property name (key)(ex: "fishtype") and the
718 // clientName of the polymorphicDiscriminator {metadata} (ex: "fishtype")
719 // instead of the serializedName of the polymorphicDiscriminator (ex: "fish.type")
720 // is a better approach. The generator is not consistent with escaping '\.' in the
721 // serializedName of the property (ex: "fish\.type") that is marked as polymorphic discriminator
722 // and the serializedName of the metadata polymorphicDiscriminator (ex: "fish.type"). However,
723 // the clientName transformation of the polymorphicDiscriminator (ex: "fishtype") and
724 // the transformation of model property name (ex: "fishtype") is done consistently.
725 // Hence, it is a safer bet to rely on the clientName of the polymorphicDiscriminator.
726 if (
727 polymorphicDiscriminator &&
728 key === polymorphicDiscriminator.clientName &&
729 propertyInstance == undefined
730 ) {
731 propertyInstance = mapper.serializedName;
732 }
733
734 let serializedValue;
735 // paging
736 if (Array.isArray(responseBody[key]) && modelProps[key].serializedName === "") {
737 propertyInstance = responseBody[key];
738 const arrayInstance = serializer.deserialize(
739 propertyMapper,
740 propertyInstance,
741 propertyObjectName
742 );
743 // Copy over any properties that have already been added into the instance, where they do
744 // not exist on the newly de-serialized array
745 for (const [key, value] of Object.entries(instance)) {
746 if (!arrayInstance.hasOwnProperty(key)) {
747 arrayInstance[key] = value;
748 }
749 }
750 instance = arrayInstance;
751 } else if (propertyInstance !== undefined || propertyMapper.defaultValue !== undefined) {
752 serializedValue = serializer.deserialize(
753 propertyMapper,
754 propertyInstance,
755 propertyObjectName
756 );
757 instance[key] = serializedValue;
758 }
759 }
760 }
761
762 const additionalPropertiesMapper = mapper.type.additionalProperties;
763 if (additionalPropertiesMapper) {
764 const isAdditionalProperty = (responsePropName: string) => {
765 for (const clientPropName in modelProps) {
766 const paths = splitSerializeName(modelProps[clientPropName].serializedName);
767 if (paths[0] === responsePropName) {
768 return false;
769 }
770 }
771 return true;
772 };
773
774 for (const responsePropName in responseBody) {
775 if (isAdditionalProperty(responsePropName)) {
776 instance[responsePropName] = serializer.deserialize(
777 additionalPropertiesMapper,
778 responseBody[responsePropName],
779 objectName + '["' + responsePropName + '"]'
780 );
781 }
782 }
783 } else if (responseBody) {
784 for (const key of Object.keys(responseBody)) {
785 if (
786 instance[key] === undefined &&
787 !handledPropertyNames.includes(key) &&
788 !isSpecialXmlProperty(key)
789 ) {
790 instance[key] = responseBody[key];
791 }
792 }
793 }
794
795 return instance;
796}
797
798function deserializeDictionaryType(
799 serializer: Serializer,
800 mapper: DictionaryMapper,
801 responseBody: any,
802 objectName: string
803): any {
804 /*jshint validthis: true */
805 const value = mapper.type.value;
806 if (!value || typeof value !== "object") {
807 throw new Error(
808 `"value" metadata for a Dictionary must be defined in the ` +
809 `mapper and it must of type "object" in ${objectName}`
810 );
811 }
812 if (responseBody) {
813 const tempDictionary: { [key: string]: any } = {};
814 for (const key of Object.keys(responseBody)) {
815 tempDictionary[key] = serializer.deserialize(value, responseBody[key], objectName);
816 }
817 return tempDictionary;
818 }
819 return responseBody;
820}
821
822function deserializeSequenceType(
823 serializer: Serializer,
824 mapper: SequenceMapper,
825 responseBody: any,
826 objectName: string
827): any {
828 /*jshint validthis: true */
829 const element = mapper.type.element;
830 if (!element || typeof element !== "object") {
831 throw new Error(
832 `element" metadata for an Array must be defined in the ` +
833 `mapper and it must of type "object" in ${objectName}`
834 );
835 }
836 if (responseBody) {
837 if (!Array.isArray(responseBody)) {
838 // xml2js will interpret a single element array as just the element, so force it to be an array
839 responseBody = [responseBody];
840 }
841
842 const tempArray = [];
843 for (let i = 0; i < responseBody.length; i++) {
844 tempArray[i] = serializer.deserialize(element, responseBody[i], `${objectName}[${i}]`);
845 }
846 return tempArray;
847 }
848 return responseBody;
849}
850
851function getPolymorphicMapper(
852 serializer: Serializer,
853 mapper: CompositeMapper,
854 object: any,
855 polymorphicPropertyName: "clientName" | "serializedName"
856): CompositeMapper {
857 const polymorphicDiscriminator = getPolymorphicDiscriminatorRecursively(serializer, mapper);
858 if (polymorphicDiscriminator) {
859 const discriminatorName = polymorphicDiscriminator[polymorphicPropertyName];
860 if (discriminatorName != undefined) {
861 const discriminatorValue = object[discriminatorName];
862 if (discriminatorValue != undefined) {
863 const typeName = mapper.type.uberParent || mapper.type.className;
864 const indexDiscriminator =
865 discriminatorValue === typeName
866 ? discriminatorValue
867 : typeName + "." + discriminatorValue;
868 const polymorphicMapper = serializer.modelMappers.discriminators[indexDiscriminator];
869 if (polymorphicMapper) {
870 mapper = polymorphicMapper;
871 }
872 }
873 }
874 }
875 return mapper;
876}
877
878function getPolymorphicDiscriminatorRecursively(
879 serializer: Serializer,
880 mapper: CompositeMapper
881): PolymorphicDiscriminator | undefined {
882 return (
883 mapper.type.polymorphicDiscriminator ||
884 getPolymorphicDiscriminatorSafely(serializer, mapper.type.uberParent) ||
885 getPolymorphicDiscriminatorSafely(serializer, mapper.type.className)
886 );
887}
888
889function getPolymorphicDiscriminatorSafely(serializer: Serializer, typeName?: string) {
890 return (
891 typeName &&
892 serializer.modelMappers[typeName] &&
893 serializer.modelMappers[typeName].type.polymorphicDiscriminator
894 );
895}
896
897export interface MapperConstraints {
898 InclusiveMaximum?: number;
899 ExclusiveMaximum?: number;
900 InclusiveMinimum?: number;
901 ExclusiveMinimum?: number;
902 MaxLength?: number;
903 MinLength?: number;
904 Pattern?: RegExp;
905 MaxItems?: number;
906 MinItems?: number;
907 UniqueItems?: true;
908 MultipleOf?: number;
909}
910
911export type MapperType =
912 | SimpleMapperType
913 | CompositeMapperType
914 | SequenceMapperType
915 | DictionaryMapperType
916 | EnumMapperType;
917
918export interface SimpleMapperType {
919 name:
920 | "Base64Url"
921 | "Boolean"
922 | "ByteArray"
923 | "Date"
924 | "DateTime"
925 | "DateTimeRfc1123"
926 | "Object"
927 | "Stream"
928 | "String"
929 | "TimeSpan"
930 | "UnixTime"
931 | "Uuid"
932 | "Number"
933 | "any";
934}
935
936export interface CompositeMapperType {
937 name: "Composite";
938
939 // Only one of the two below properties should be present.
940 // Use className to reference another type definition,
941 // and use modelProperties/additionalProperties when the reference to the other type has been resolved.
942 className?: string;
943
944 modelProperties?: { [propertyName: string]: Mapper };
945 additionalProperties?: Mapper;
946
947 uberParent?: string;
948 polymorphicDiscriminator?: PolymorphicDiscriminator;
949}
950
951export interface SequenceMapperType {
952 name: "Sequence";
953 element: Mapper;
954}
955
956export interface DictionaryMapperType {
957 name: "Dictionary";
958 value: Mapper;
959}
960
961export interface EnumMapperType {
962 name: "Enum";
963 allowedValues: any[];
964}
965
966export interface BaseMapper {
967 xmlName?: string;
968 xmlIsAttribute?: boolean;
969 xmlElementName?: string;
970 xmlIsWrapped?: boolean;
971 readOnly?: boolean;
972 isConstant?: boolean;
973 required?: boolean;
974 nullable?: boolean;
975 serializedName?: string;
976 type: MapperType;
977 defaultValue?: any;
978 constraints?: MapperConstraints;
979}
980
981export type Mapper = BaseMapper | CompositeMapper | SequenceMapper | DictionaryMapper | EnumMapper;
982
983export interface PolymorphicDiscriminator {
984 serializedName: string;
985 clientName: string;
986 [key: string]: string;
987}
988
989export interface CompositeMapper extends BaseMapper {
990 type: CompositeMapperType;
991}
992
993export interface SequenceMapper extends BaseMapper {
994 type: SequenceMapperType;
995}
996
997export interface DictionaryMapper extends BaseMapper {
998 type: DictionaryMapperType;
999 headerCollectionPrefix?: string;
1000}
1001
1002export interface EnumMapper extends BaseMapper {
1003 type: EnumMapperType;
1004}
1005
1006export interface UrlParameterValue {
1007 value: string;
1008 skipUrlEncoding: boolean;
1009}
1010
1011// TODO: why is this here?
1012export function serializeObject(toSerialize: any): any {
1013 if (toSerialize == undefined) return undefined;
1014 if (toSerialize instanceof Uint8Array) {
1015 toSerialize = base64.encodeByteArray(toSerialize);
1016 return toSerialize;
1017 } else if (toSerialize instanceof Date) {
1018 return toSerialize.toISOString();
1019 } else if (Array.isArray(toSerialize)) {
1020 const array = [];
1021 for (let i = 0; i < toSerialize.length; i++) {
1022 array.push(serializeObject(toSerialize[i]));
1023 }
1024 return array;
1025 } else if (typeof toSerialize === "object") {
1026 const dictionary: { [key: string]: any } = {};
1027 for (const property in toSerialize) {
1028 dictionary[property] = serializeObject(toSerialize[property]);
1029 }
1030 return dictionary;
1031 }
1032 return toSerialize;
1033}
1034
1035/**
1036 * Utility function to create a K:V from a list of strings
1037 */
1038function strEnum<T extends string>(o: Array<T>): { [K in T]: K } {
1039 const result: any = {};
1040 for (const key of o) {
1041 result[key] = key;
1042 }
1043 return result;
1044}
1045
1046export const MapperType = strEnum([
1047 "Base64Url",
1048 "Boolean",
1049 "ByteArray",
1050 "Composite",
1051 "Date",
1052 "DateTime",
1053 "DateTimeRfc1123",
1054 "Dictionary",
1055 "Enum",
1056 "Number",
1057 "Object",
1058 "Sequence",
1059 "String",
1060 "Stream",
1061 "TimeSpan",
1062 "UnixTime",
1063]);