serializer.ts 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873
  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. import * as base64 from "./util/base64";
  4. import * as utils from "./util/utils";
  5. export class Serializer {
  6. constructor(public readonly modelMappers: { [key: string]: any } = {}, public readonly isXML?: boolean) { }
  7. validateConstraints(mapper: Mapper, value: any, objectName: string): void {
  8. const failValidation = (constraintName: keyof MapperConstraints, constraintValue: any) => {
  9. throw new Error(`"${objectName}" with value "${value}" should satisfy the constraint "${constraintName}": ${constraintValue}.`);
  10. };
  11. if (mapper.constraints && (value != undefined)) {
  12. const {
  13. ExclusiveMaximum,
  14. ExclusiveMinimum,
  15. InclusiveMaximum,
  16. InclusiveMinimum,
  17. MaxItems,
  18. MaxLength,
  19. MinItems,
  20. MinLength,
  21. MultipleOf,
  22. Pattern,
  23. UniqueItems
  24. } = mapper.constraints;
  25. if (ExclusiveMaximum != undefined && value >= ExclusiveMaximum) {
  26. failValidation("ExclusiveMaximum", ExclusiveMaximum);
  27. }
  28. if (ExclusiveMinimum != undefined && value <= ExclusiveMinimum) {
  29. failValidation("ExclusiveMinimum", ExclusiveMinimum);
  30. }
  31. if (InclusiveMaximum != undefined && value > InclusiveMaximum) {
  32. failValidation("InclusiveMaximum", InclusiveMaximum);
  33. }
  34. if (InclusiveMinimum != undefined && value < InclusiveMinimum) {
  35. failValidation("InclusiveMinimum", InclusiveMinimum);
  36. }
  37. if (MaxItems != undefined && value.length > MaxItems) {
  38. failValidation("MaxItems", MaxItems);
  39. }
  40. if (MaxLength != undefined && value.length > MaxLength) {
  41. failValidation("MaxLength", MaxLength);
  42. }
  43. if (MinItems != undefined && value.length < MinItems) {
  44. failValidation("MinItems", MinItems);
  45. }
  46. if (MinLength != undefined && value.length < MinLength) {
  47. failValidation("MinLength", MinLength);
  48. }
  49. if (MultipleOf != undefined && value % MultipleOf !== 0) {
  50. failValidation("MultipleOf", MultipleOf);
  51. }
  52. if (Pattern && value.match(Pattern) === null) {
  53. failValidation("Pattern", Pattern);
  54. }
  55. if (UniqueItems && value.some((item: any, i: number, ar: Array<any>) => ar.indexOf(item) !== i)) {
  56. failValidation("UniqueItems", UniqueItems);
  57. }
  58. }
  59. }
  60. /**
  61. * Serialize the given object based on its metadata defined in the mapper
  62. *
  63. * @param {Mapper} mapper The mapper which defines the metadata of the serializable object
  64. *
  65. * @param {object|string|Array|number|boolean|Date|stream} object A valid Javascript object to be serialized
  66. *
  67. * @param {string} objectName Name of the serialized object
  68. *
  69. * @returns {object|string|Array|number|boolean|Date|stream} A valid serialized Javascript object
  70. */
  71. serialize(mapper: Mapper, object: any, objectName?: string): any {
  72. let payload: any = {};
  73. const mapperType = mapper.type.name as string;
  74. if (!objectName) {
  75. objectName = mapper.serializedName!;
  76. }
  77. if (mapperType.match(/^Sequence$/ig) !== null) {
  78. payload = [];
  79. }
  80. if (object == undefined && (mapper.defaultValue != undefined || mapper.isConstant)) {
  81. object = mapper.defaultValue;
  82. }
  83. // This table of allowed values should help explain
  84. // the mapper.required and mapper.nullable properties.
  85. // X means "neither undefined or null are allowed".
  86. // || required
  87. // || true | false
  88. // nullable || ==========================
  89. // true || null | undefined/null
  90. // false || X | undefined
  91. // undefined || X | undefined/null
  92. const { required, nullable } = mapper;
  93. if (required && nullable && object === undefined) {
  94. throw new Error(`${objectName} cannot be undefined.`);
  95. }
  96. if (required && !nullable && object == undefined) {
  97. throw new Error(`${objectName} cannot be null or undefined.`);
  98. }
  99. if (!required && nullable === false && object === null) {
  100. throw new Error(`${objectName} cannot be null.`);
  101. }
  102. if (object == undefined) {
  103. payload = object;
  104. } else {
  105. // Validate Constraints if any
  106. this.validateConstraints(mapper, object, objectName);
  107. if (mapperType.match(/^any$/ig) !== null) {
  108. payload = object;
  109. } else if (mapperType.match(/^(Number|String|Boolean|Object|Stream|Uuid)$/ig) !== null) {
  110. payload = serializeBasicTypes(mapperType, objectName, object);
  111. } else if (mapperType.match(/^Enum$/ig) !== null) {
  112. const enumMapper: EnumMapper = mapper as EnumMapper;
  113. payload = serializeEnumType(objectName, enumMapper.type.allowedValues, object);
  114. } else if (mapperType.match(/^(Date|DateTime|TimeSpan|DateTimeRfc1123|UnixTime)$/ig) !== null) {
  115. payload = serializeDateTypes(mapperType, object, objectName);
  116. } else if (mapperType.match(/^ByteArray$/ig) !== null) {
  117. payload = serializeByteArrayType(objectName, object);
  118. } else if (mapperType.match(/^Base64Url$/ig) !== null) {
  119. payload = serializeBase64UrlType(objectName, object);
  120. } else if (mapperType.match(/^Sequence$/ig) !== null) {
  121. payload = serializeSequenceType(this, mapper as SequenceMapper, object, objectName);
  122. } else if (mapperType.match(/^Dictionary$/ig) !== null) {
  123. payload = serializeDictionaryType(this, mapper as DictionaryMapper, object, objectName);
  124. } else if (mapperType.match(/^Composite$/ig) !== null) {
  125. payload = serializeCompositeType(this, mapper as CompositeMapper, object, objectName);
  126. }
  127. }
  128. return payload;
  129. }
  130. /**
  131. * Deserialize the given object based on its metadata defined in the mapper
  132. *
  133. * @param {object} mapper The mapper which defines the metadata of the serializable object
  134. *
  135. * @param {object|string|Array|number|boolean|Date|stream} responseBody A valid Javascript entity to be deserialized
  136. *
  137. * @param {string} objectName Name of the deserialized object
  138. *
  139. * @returns {object|string|Array|number|boolean|Date|stream} A valid deserialized Javascript object
  140. */
  141. deserialize(mapper: Mapper, responseBody: any, objectName: string): any {
  142. if (responseBody == undefined) {
  143. if (this.isXML && mapper.type.name === "Sequence" && !mapper.xmlIsWrapped) {
  144. // Edge case for empty XML non-wrapped lists. xml2js can't distinguish
  145. // between the list being empty versus being missing,
  146. // so let's do the more user-friendly thing and return an empty list.
  147. responseBody = [];
  148. }
  149. return responseBody;
  150. }
  151. let payload: any;
  152. const mapperType = mapper.type.name;
  153. if (!objectName) {
  154. objectName = mapper.serializedName!;
  155. }
  156. if (mapperType.match(/^Composite$/ig) !== null) {
  157. payload = deserializeCompositeType(this, mapper as CompositeMapper, responseBody, objectName);
  158. } else {
  159. if (this.isXML) {
  160. /**
  161. * If the mapper specifies this as a non-composite type value but the responseBody contains
  162. * both header ("$") and body ("_") properties, then just reduce the responseBody value to
  163. * the body ("_") property.
  164. */
  165. if (responseBody["$"] != undefined && responseBody["_"] != undefined) {
  166. responseBody = responseBody["_"];
  167. }
  168. }
  169. if (mapperType.match(/^Number$/ig) !== null) {
  170. payload = parseFloat(responseBody);
  171. if (isNaN(payload)) {
  172. payload = responseBody;
  173. }
  174. } else if (mapperType.match(/^Boolean$/ig) !== null) {
  175. if (responseBody === "true") {
  176. payload = true;
  177. } else if (responseBody === "false") {
  178. payload = false;
  179. } else {
  180. payload = responseBody;
  181. }
  182. } else if (mapperType.match(/^(String|Enum|Object|Stream|Uuid|TimeSpan|any)$/ig) !== null) {
  183. payload = responseBody;
  184. } else if (mapperType.match(/^(Date|DateTime|DateTimeRfc1123)$/ig) !== null) {
  185. payload = new Date(responseBody);
  186. } else if (mapperType.match(/^UnixTime$/ig) !== null) {
  187. payload = unixTimeToDate(responseBody);
  188. } else if (mapperType.match(/^ByteArray$/ig) !== null) {
  189. payload = base64.decodeString(responseBody);
  190. } else if (mapperType.match(/^Base64Url$/ig) !== null) {
  191. payload = base64UrlToByteArray(responseBody);
  192. } else if (mapperType.match(/^Sequence$/ig) !== null) {
  193. payload = deserializeSequenceType(this, mapper as SequenceMapper, responseBody, objectName);
  194. } else if (mapperType.match(/^Dictionary$/ig) !== null) {
  195. payload = deserializeDictionaryType(this, mapper as DictionaryMapper, responseBody, objectName);
  196. }
  197. }
  198. if (mapper.isConstant) {
  199. payload = mapper.defaultValue;
  200. }
  201. return payload;
  202. }
  203. }
  204. function trimEnd(str: string, ch: string) {
  205. let len = str.length;
  206. while ((len - 1) >= 0 && str[len - 1] === ch) {
  207. --len;
  208. }
  209. return str.substr(0, len);
  210. }
  211. function bufferToBase64Url(buffer: any): string | undefined {
  212. if (!buffer) {
  213. return undefined;
  214. }
  215. if (!(buffer instanceof Uint8Array)) {
  216. throw new Error(`Please provide an input of type Uint8Array for converting to Base64Url.`);
  217. }
  218. // Uint8Array to Base64.
  219. const str = base64.encodeByteArray(buffer);
  220. // Base64 to Base64Url.
  221. return trimEnd(str, "=").replace(/\+/g, "-").replace(/\//g, "_");
  222. }
  223. function base64UrlToByteArray(str: string): Uint8Array | undefined {
  224. if (!str) {
  225. return undefined;
  226. }
  227. if (str && typeof str.valueOf() !== "string") {
  228. throw new Error("Please provide an input of type string for converting to Uint8Array");
  229. }
  230. // Base64Url to Base64.
  231. str = str.replace(/\-/g, "+").replace(/\_/g, "/");
  232. // Base64 to Uint8Array.
  233. return base64.decodeString(str);
  234. }
  235. function splitSerializeName(prop: string | undefined): string[] {
  236. const classes: string[] = [];
  237. let partialclass = "";
  238. if (prop) {
  239. const subwords = prop.split(".");
  240. for (const item of subwords) {
  241. if (item.charAt(item.length - 1) === "\\") {
  242. partialclass += item.substr(0, item.length - 1) + ".";
  243. } else {
  244. partialclass += item;
  245. classes.push(partialclass);
  246. partialclass = "";
  247. }
  248. }
  249. }
  250. return classes;
  251. }
  252. function dateToUnixTime(d: string | Date): number | undefined {
  253. if (!d) {
  254. return undefined;
  255. }
  256. if (typeof d.valueOf() === "string") {
  257. d = new Date(d as string);
  258. }
  259. return Math.floor((d as Date).getTime() / 1000);
  260. }
  261. function unixTimeToDate(n: number): Date | undefined {
  262. if (!n) {
  263. return undefined;
  264. }
  265. return new Date(n * 1000);
  266. }
  267. function serializeBasicTypes(typeName: string, objectName: string, value: any): any {
  268. if (value !== null && value !== undefined) {
  269. if (typeName.match(/^Number$/ig) !== null) {
  270. if (typeof value !== "number") {
  271. throw new Error(`${objectName} with value ${value} must be of type number.`);
  272. }
  273. } else if (typeName.match(/^String$/ig) !== null) {
  274. if (typeof value.valueOf() !== "string") {
  275. throw new Error(`${objectName} with value "${value}" must be of type string.`);
  276. }
  277. } else if (typeName.match(/^Uuid$/ig) !== null) {
  278. if (!(typeof value.valueOf() === "string" && utils.isValidUuid(value))) {
  279. throw new Error(`${objectName} with value "${value}" must be of type string and a valid uuid.`);
  280. }
  281. } else if (typeName.match(/^Boolean$/ig) !== null) {
  282. if (typeof value !== "boolean") {
  283. throw new Error(`${objectName} with value ${value} must be of type boolean.`);
  284. }
  285. } else if (typeName.match(/^Stream$/ig) !== null) {
  286. const objectType = typeof value;
  287. if (objectType !== "string" &&
  288. objectType !== "function" &&
  289. !(value instanceof ArrayBuffer) &&
  290. !ArrayBuffer.isView(value) &&
  291. !(typeof Blob === "function" && value instanceof Blob)) {
  292. throw new Error(`${objectName} must be a string, Blob, ArrayBuffer, ArrayBufferView, or a function returning NodeJS.ReadableStream.`);
  293. }
  294. }
  295. }
  296. return value;
  297. }
  298. function serializeEnumType(objectName: string, allowedValues: Array<any>, value: any): any {
  299. if (!allowedValues) {
  300. throw new Error(`Please provide a set of allowedValues to validate ${objectName} as an Enum Type.`);
  301. }
  302. const isPresent = allowedValues.some((item) => {
  303. if (typeof item.valueOf() === "string") {
  304. return item.toLowerCase() === value.toLowerCase();
  305. }
  306. return item === value;
  307. });
  308. if (!isPresent) {
  309. throw new Error(`${value} is not a valid value for ${objectName}. The valid values are: ${JSON.stringify(allowedValues)}.`);
  310. }
  311. return value;
  312. }
  313. function serializeByteArrayType(objectName: string, value: any): any {
  314. if (value != undefined) {
  315. if (!(value instanceof Uint8Array)) {
  316. throw new Error(`${objectName} must be of type Uint8Array.`);
  317. }
  318. value = base64.encodeByteArray(value);
  319. }
  320. return value;
  321. }
  322. function serializeBase64UrlType(objectName: string, value: any): any {
  323. if (value != undefined) {
  324. if (!(value instanceof Uint8Array)) {
  325. throw new Error(`${objectName} must be of type Uint8Array.`);
  326. }
  327. value = bufferToBase64Url(value);
  328. }
  329. return value;
  330. }
  331. function serializeDateTypes(typeName: string, value: any, objectName: string) {
  332. if (value != undefined) {
  333. if (typeName.match(/^Date$/ig) !== null) {
  334. if (!(value instanceof Date ||
  335. (typeof value.valueOf() === "string" && !isNaN(Date.parse(value))))) {
  336. throw new Error(`${objectName} must be an instanceof Date or a string in ISO8601 format.`);
  337. }
  338. value = (value instanceof Date) ? value.toISOString().substring(0, 10) : new Date(value).toISOString().substring(0, 10);
  339. } else if (typeName.match(/^DateTime$/ig) !== null) {
  340. if (!(value instanceof Date ||
  341. (typeof value.valueOf() === "string" && !isNaN(Date.parse(value))))) {
  342. throw new Error(`${objectName} must be an instanceof Date or a string in ISO8601 format.`);
  343. }
  344. value = (value instanceof Date) ? value.toISOString() : new Date(value).toISOString();
  345. } else if (typeName.match(/^DateTimeRfc1123$/ig) !== null) {
  346. if (!(value instanceof Date ||
  347. (typeof value.valueOf() === "string" && !isNaN(Date.parse(value))))) {
  348. throw new Error(`${objectName} must be an instanceof Date or a string in RFC-1123 format.`);
  349. }
  350. value = (value instanceof Date) ? value.toUTCString() : new Date(value).toUTCString();
  351. } else if (typeName.match(/^UnixTime$/ig) !== null) {
  352. if (!(value instanceof Date ||
  353. (typeof value.valueOf() === "string" && !isNaN(Date.parse(value))))) {
  354. throw new Error(`${objectName} must be an instanceof Date or a string in RFC-1123/ISO8601 format ` +
  355. `for it to be serialized in UnixTime/Epoch format.`);
  356. }
  357. value = dateToUnixTime(value);
  358. } else if (typeName.match(/^TimeSpan$/ig) !== null) {
  359. if (!utils.isDuration(value)) {
  360. throw new Error(`${objectName} must be a string in ISO 8601 format. Instead was "${value}".`);
  361. }
  362. value = value;
  363. }
  364. }
  365. return value;
  366. }
  367. function serializeSequenceType(serializer: Serializer, mapper: SequenceMapper, object: any, objectName: string) {
  368. if (!Array.isArray(object)) {
  369. throw new Error(`${objectName} must be of type Array.`);
  370. }
  371. const elementType = mapper.type.element;
  372. if (!elementType || typeof elementType !== "object") {
  373. throw new Error(`element" metadata for an Array must be defined in the ` +
  374. `mapper and it must of type "object" in ${objectName}.`);
  375. }
  376. const tempArray = [];
  377. for (let i = 0; i < object.length; i++) {
  378. tempArray[i] = serializer.serialize(elementType, object[i], objectName);
  379. }
  380. return tempArray;
  381. }
  382. function serializeDictionaryType(serializer: Serializer, mapper: DictionaryMapper, object: any, objectName: string) {
  383. if (typeof object !== "object") {
  384. throw new Error(`${objectName} must be of type object.`);
  385. }
  386. const valueType = mapper.type.value;
  387. if (!valueType || typeof valueType !== "object") {
  388. throw new Error(`"value" metadata for a Dictionary must be defined in the ` +
  389. `mapper and it must of type "object" in ${objectName}.`);
  390. }
  391. const tempDictionary: { [key: string]: any } = {};
  392. for (const key of Object.keys(object)) {
  393. tempDictionary[key] = serializer.serialize(valueType, object[key], objectName + "." + key);
  394. }
  395. return tempDictionary;
  396. }
  397. /**
  398. * Resolves a composite mapper's modelProperties.
  399. * @param serializer the serializer containing the entire set of mappers
  400. * @param mapper the composite mapper to resolve
  401. */
  402. function resolveModelProperties(serializer: Serializer, mapper: CompositeMapper, objectName: string): { [propertyName: string]: Mapper } {
  403. let modelProps = mapper.type.modelProperties;
  404. if (!modelProps) {
  405. const className = mapper.type.className;
  406. if (!className) {
  407. throw new Error(`Class name for model "${objectName}" is not provided in the mapper "${JSON.stringify(mapper, undefined, 2)}".`);
  408. }
  409. const modelMapper = serializer.modelMappers[className];
  410. if (!modelMapper) {
  411. throw new Error(`mapper() cannot be null or undefined for model "${className}".`);
  412. }
  413. modelProps = modelMapper.type.modelProperties;
  414. if (!modelProps) {
  415. throw new Error(`modelProperties cannot be null or undefined in the ` +
  416. `mapper "${JSON.stringify(modelMapper)}" of type "${className}" for object "${objectName}".`);
  417. }
  418. }
  419. return modelProps;
  420. }
  421. function serializeCompositeType(serializer: Serializer, mapper: CompositeMapper, object: any, objectName: string) {
  422. if (getPolymorphicDiscriminatorRecursively(serializer, mapper)) {
  423. mapper = getPolymorphicMapper(serializer, mapper, object, "clientName");
  424. }
  425. if (object != undefined) {
  426. const payload: any = {};
  427. const modelProps = resolveModelProperties(serializer, mapper, objectName);
  428. for (const key of Object.keys(modelProps)) {
  429. const propertyMapper = modelProps[key];
  430. if (propertyMapper.readOnly) {
  431. continue;
  432. }
  433. let propName: string | undefined;
  434. let parentObject: any = payload;
  435. if (serializer.isXML) {
  436. if (propertyMapper.xmlIsWrapped) {
  437. propName = propertyMapper.xmlName;
  438. } else {
  439. propName = propertyMapper.xmlElementName || propertyMapper.xmlName;
  440. }
  441. } else {
  442. const paths = splitSerializeName(propertyMapper.serializedName!);
  443. propName = paths.pop();
  444. for (const pathName of paths) {
  445. const childObject = parentObject[pathName];
  446. if ((childObject == undefined) && (object[key] != undefined)) {
  447. parentObject[pathName] = {};
  448. }
  449. parentObject = parentObject[pathName];
  450. }
  451. }
  452. if (parentObject != undefined) {
  453. const propertyObjectName = propertyMapper.serializedName !== ""
  454. ? objectName + "." + propertyMapper.serializedName
  455. : objectName;
  456. let toSerialize = object[key];
  457. const polymorphicDiscriminator = getPolymorphicDiscriminatorRecursively(serializer, mapper);
  458. if (polymorphicDiscriminator && polymorphicDiscriminator.clientName === key && toSerialize == undefined) {
  459. toSerialize = mapper.serializedName;
  460. }
  461. const serializedValue = serializer.serialize(propertyMapper, toSerialize, propertyObjectName);
  462. if (serializedValue !== undefined && propName != undefined) {
  463. if (propertyMapper.xmlIsAttribute) {
  464. // $ is the key attributes are kept under in xml2js.
  465. // This keeps things simple while preventing name collision
  466. // with names in user documents.
  467. parentObject.$ = parentObject.$ || {};
  468. parentObject.$[propName] = serializedValue;
  469. } else if (propertyMapper.xmlIsWrapped) {
  470. parentObject[propName] = { [propertyMapper.xmlElementName!]: serializedValue };
  471. } else {
  472. parentObject[propName] = serializedValue;
  473. }
  474. }
  475. }
  476. }
  477. const additionalPropertiesMapper = mapper.type.additionalProperties;
  478. if (additionalPropertiesMapper) {
  479. const propNames = Object.keys(modelProps);
  480. for (const clientPropName in object) {
  481. const isAdditionalProperty = propNames.every(pn => pn !== clientPropName);
  482. if (isAdditionalProperty) {
  483. payload[clientPropName] = serializer.serialize(additionalPropertiesMapper, object[clientPropName], objectName + '["' + clientPropName + '"]');
  484. }
  485. }
  486. }
  487. return payload;
  488. }
  489. return object;
  490. }
  491. function isSpecialXmlProperty(propertyName: string): boolean {
  492. return ["$", "_"].includes(propertyName);
  493. }
  494. function deserializeCompositeType(serializer: Serializer, mapper: CompositeMapper, responseBody: any, objectName: string): any {
  495. if (getPolymorphicDiscriminatorRecursively(serializer, mapper)) {
  496. mapper = getPolymorphicMapper(serializer, mapper, responseBody, "serializedName");
  497. }
  498. const modelProps = resolveModelProperties(serializer, mapper, objectName);
  499. let instance: { [key: string]: any } = {};
  500. const handledPropertyNames: string[] = [];
  501. for (const key of Object.keys(modelProps)) {
  502. const propertyMapper = modelProps[key];
  503. const paths = splitSerializeName(modelProps[key].serializedName!);
  504. handledPropertyNames.push(paths[0]);
  505. const { serializedName, xmlName, xmlElementName } = propertyMapper;
  506. let propertyObjectName = objectName;
  507. if (serializedName !== "" && serializedName !== undefined) {
  508. propertyObjectName = objectName + "." + serializedName;
  509. }
  510. const headerCollectionPrefix = (propertyMapper as DictionaryMapper).headerCollectionPrefix;
  511. if (headerCollectionPrefix) {
  512. const dictionary: any = {};
  513. for (const headerKey of Object.keys(responseBody)) {
  514. if (headerKey.startsWith(headerCollectionPrefix)) {
  515. dictionary[headerKey.substring(headerCollectionPrefix.length)] = serializer.deserialize((propertyMapper as DictionaryMapper).type.value, responseBody[headerKey], propertyObjectName);
  516. }
  517. handledPropertyNames.push(headerKey);
  518. }
  519. instance[key] = dictionary;
  520. } else if (serializer.isXML) {
  521. if (propertyMapper.xmlIsAttribute && responseBody.$) {
  522. instance[key] = serializer.deserialize(propertyMapper, responseBody.$[xmlName!], propertyObjectName);
  523. } else {
  524. const propertyName = xmlElementName || xmlName || serializedName;
  525. let unwrappedProperty = responseBody[propertyName!];
  526. if (propertyMapper.xmlIsWrapped) {
  527. unwrappedProperty = responseBody[xmlName!];
  528. unwrappedProperty = unwrappedProperty && unwrappedProperty[xmlElementName!];
  529. const isEmptyWrappedList = unwrappedProperty === undefined;
  530. if (isEmptyWrappedList) {
  531. unwrappedProperty = [];
  532. }
  533. }
  534. instance[key] = serializer.deserialize(propertyMapper, unwrappedProperty, propertyObjectName);
  535. }
  536. } else {
  537. // deserialize the property if it is present in the provided responseBody instance
  538. let propertyInstance;
  539. let res = responseBody;
  540. // traversing the object step by step.
  541. for (const item of paths) {
  542. if (!res) break;
  543. res = res[item];
  544. }
  545. propertyInstance = res;
  546. const polymorphicDiscriminator = mapper.type.polymorphicDiscriminator;
  547. if (polymorphicDiscriminator && propertyMapper.serializedName === polymorphicDiscriminator.serializedName && propertyInstance == undefined) {
  548. propertyInstance = mapper.serializedName;
  549. }
  550. let serializedValue;
  551. // paging
  552. if (Array.isArray(responseBody[key]) && modelProps[key].serializedName === "") {
  553. propertyInstance = responseBody[key];
  554. instance = serializer.deserialize(propertyMapper, propertyInstance, propertyObjectName);
  555. } else if (propertyInstance !== undefined) {
  556. serializedValue = serializer.deserialize(propertyMapper, propertyInstance, propertyObjectName);
  557. instance[key] = serializedValue;
  558. }
  559. }
  560. }
  561. const additionalPropertiesMapper = mapper.type.additionalProperties;
  562. if (additionalPropertiesMapper) {
  563. const isAdditionalProperty = (responsePropName: string) => {
  564. for (const clientPropName in modelProps) {
  565. const paths = splitSerializeName(modelProps[clientPropName].serializedName);
  566. if (paths[0] === responsePropName) {
  567. return false;
  568. }
  569. }
  570. return true;
  571. };
  572. for (const responsePropName in responseBody) {
  573. if (isAdditionalProperty(responsePropName)) {
  574. instance[responsePropName] = serializer.deserialize(additionalPropertiesMapper, responseBody[responsePropName], objectName + '["' + responsePropName + '"]');
  575. }
  576. }
  577. } else if (responseBody) {
  578. for (const key of Object.keys(responseBody)) {
  579. if (instance[key] === undefined && !handledPropertyNames.includes(key) && !isSpecialXmlProperty(key)) {
  580. instance[key] = responseBody[key];
  581. }
  582. }
  583. }
  584. return instance;
  585. }
  586. function deserializeDictionaryType(serializer: Serializer, mapper: DictionaryMapper, responseBody: any, objectName: string): any {
  587. /*jshint validthis: true */
  588. const value = mapper.type.value;
  589. if (!value || typeof value !== "object") {
  590. throw new Error(`"value" metadata for a Dictionary must be defined in the ` +
  591. `mapper and it must of type "object" in ${objectName}`);
  592. }
  593. if (responseBody) {
  594. const tempDictionary: { [key: string]: any } = {};
  595. for (const key of Object.keys(responseBody)) {
  596. tempDictionary[key] = serializer.deserialize(value, responseBody[key], objectName);
  597. }
  598. return tempDictionary;
  599. }
  600. return responseBody;
  601. }
  602. function deserializeSequenceType(serializer: Serializer, mapper: SequenceMapper, responseBody: any, objectName: string): any {
  603. /*jshint validthis: true */
  604. const element = mapper.type.element;
  605. if (!element || typeof element !== "object") {
  606. throw new Error(`element" metadata for an Array must be defined in the ` +
  607. `mapper and it must of type "object" in ${objectName}`);
  608. }
  609. if (responseBody) {
  610. if (!Array.isArray(responseBody)) {
  611. // xml2js will interpret a single element array as just the element, so force it to be an array
  612. responseBody = [responseBody];
  613. }
  614. const tempArray = [];
  615. for (let i = 0; i < responseBody.length; i++) {
  616. tempArray[i] = serializer.deserialize(element, responseBody[i], `${objectName}[${i}]`);
  617. }
  618. return tempArray;
  619. }
  620. return responseBody;
  621. }
  622. function getPolymorphicMapper(serializer: Serializer, mapper: CompositeMapper, object: any, polymorphicPropertyName: "clientName" | "serializedName"): CompositeMapper {
  623. const polymorphicDiscriminator = getPolymorphicDiscriminatorRecursively(serializer, mapper);
  624. if (polymorphicDiscriminator) {
  625. const discriminatorName = polymorphicDiscriminator[polymorphicPropertyName];
  626. if (discriminatorName != undefined) {
  627. const discriminatorValue = object[discriminatorName];
  628. if (discriminatorValue != undefined) {
  629. const typeName = mapper.type.uberParent || mapper.type.className;
  630. const indexDiscriminator = discriminatorValue === typeName
  631. ? discriminatorValue
  632. : typeName + "." + discriminatorValue;
  633. const polymorphicMapper = serializer.modelMappers.discriminators[indexDiscriminator];
  634. if (polymorphicMapper) {
  635. mapper = polymorphicMapper;
  636. }
  637. }
  638. }
  639. }
  640. return mapper;
  641. }
  642. function getPolymorphicDiscriminatorRecursively(serializer: Serializer, mapper: CompositeMapper): PolymorphicDiscriminator | undefined {
  643. return mapper.type.polymorphicDiscriminator
  644. || getPolymorphicDiscriminatorSafely(serializer, mapper.type.uberParent)
  645. || getPolymorphicDiscriminatorSafely(serializer, mapper.type.className);
  646. }
  647. function getPolymorphicDiscriminatorSafely(serializer: Serializer, typeName?: string) {
  648. return (typeName && serializer.modelMappers[typeName] && serializer.modelMappers[typeName].type.polymorphicDiscriminator);
  649. }
  650. export interface MapperConstraints {
  651. InclusiveMaximum?: number;
  652. ExclusiveMaximum?: number;
  653. InclusiveMinimum?: number;
  654. ExclusiveMinimum?: number;
  655. MaxLength?: number;
  656. MinLength?: number;
  657. Pattern?: RegExp;
  658. MaxItems?: number;
  659. MinItems?: number;
  660. UniqueItems?: true;
  661. MultipleOf?: number;
  662. }
  663. export type MapperType = SimpleMapperType | CompositeMapperType | SequenceMapperType | DictionaryMapperType | EnumMapperType;
  664. export interface SimpleMapperType {
  665. name: "Base64Url"
  666. | "Boolean"
  667. | "ByteArray"
  668. | "Date"
  669. | "DateTime"
  670. | "DateTimeRfc1123"
  671. | "Object"
  672. | "Stream"
  673. | "String"
  674. | "TimeSpan"
  675. | "UnixTime"
  676. | "Uuid"
  677. | "Number"
  678. | "any";
  679. }
  680. export interface CompositeMapperType {
  681. name: "Composite";
  682. // Only one of the two below properties should be present.
  683. // Use className to reference another type definition,
  684. // and use modelProperties/additionalProperties when the reference to the other type has been resolved.
  685. className?: string;
  686. modelProperties?: { [propertyName: string]: Mapper };
  687. additionalProperties?: Mapper;
  688. uberParent?: string;
  689. polymorphicDiscriminator?: PolymorphicDiscriminator;
  690. }
  691. export interface SequenceMapperType {
  692. name: "Sequence";
  693. element: Mapper;
  694. }
  695. export interface DictionaryMapperType {
  696. name: "Dictionary";
  697. value: Mapper;
  698. }
  699. export interface EnumMapperType {
  700. name: "Enum";
  701. allowedValues: any[];
  702. }
  703. export interface BaseMapper {
  704. xmlName?: string;
  705. xmlIsAttribute?: boolean;
  706. xmlElementName?: string;
  707. xmlIsWrapped?: boolean;
  708. readOnly?: boolean;
  709. isConstant?: boolean;
  710. required?: boolean;
  711. nullable?: boolean;
  712. serializedName?: string;
  713. type: MapperType;
  714. defaultValue?: any;
  715. constraints?: MapperConstraints;
  716. }
  717. export type Mapper = BaseMapper | CompositeMapper | SequenceMapper | DictionaryMapper | EnumMapper;
  718. export interface PolymorphicDiscriminator {
  719. serializedName: string;
  720. clientName: string;
  721. [key: string]: string;
  722. }
  723. export interface CompositeMapper extends BaseMapper {
  724. type: CompositeMapperType;
  725. }
  726. export interface SequenceMapper extends BaseMapper {
  727. type: SequenceMapperType;
  728. }
  729. export interface DictionaryMapper extends BaseMapper {
  730. type: DictionaryMapperType;
  731. headerCollectionPrefix?: string;
  732. }
  733. export interface EnumMapper extends BaseMapper {
  734. type: EnumMapperType;
  735. }
  736. export interface UrlParameterValue {
  737. value: string;
  738. skipUrlEncoding: boolean;
  739. }
  740. // TODO: why is this here?
  741. export function serializeObject(toSerialize: any): any {
  742. if (toSerialize == undefined) return undefined;
  743. if (toSerialize instanceof Uint8Array) {
  744. toSerialize = base64.encodeByteArray(toSerialize);
  745. return toSerialize;
  746. }
  747. else if (toSerialize instanceof Date) {
  748. return toSerialize.toISOString();
  749. }
  750. else if (Array.isArray(toSerialize)) {
  751. const array = [];
  752. for (let i = 0; i < toSerialize.length; i++) {
  753. array.push(serializeObject(toSerialize[i]));
  754. }
  755. return array;
  756. } else if (typeof toSerialize === "object") {
  757. const dictionary: { [key: string]: any } = {};
  758. for (const property in toSerialize) {
  759. dictionary[property] = serializeObject(toSerialize[property]);
  760. }
  761. return dictionary;
  762. }
  763. return toSerialize;
  764. }
  765. /**
  766. * Utility function to create a K:V from a list of strings
  767. */
  768. function strEnum<T extends string>(o: Array<T>): { [K in T]: K } {
  769. const result: any = {};
  770. for (const key of o) {
  771. result[key] = key;
  772. }
  773. return result;
  774. }
  775. export const MapperType = strEnum([
  776. "Base64Url",
  777. "Boolean",
  778. "ByteArray",
  779. "Composite",
  780. "Date",
  781. "DateTime",
  782. "DateTimeRfc1123",
  783. "Dictionary",
  784. "Enum",
  785. "Number",
  786. "Object",
  787. "Sequence",
  788. "String",
  789. "Stream",
  790. "TimeSpan",
  791. "UnixTime"
  792. ]);