@azure-tools/typespec-ts 0.48.0 → 0.48.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/src/index.d.ts.map +1 -1
  3. package/dist/src/index.js +20 -5
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/modular/buildClientContext.d.ts.map +1 -1
  6. package/dist/src/modular/buildClientContext.js +7 -3
  7. package/dist/src/modular/buildClientContext.js.map +1 -1
  8. package/dist/src/modular/buildOperations.d.ts.map +1 -1
  9. package/dist/src/modular/buildOperations.js +24 -3
  10. package/dist/src/modular/buildOperations.js.map +1 -1
  11. package/dist/src/modular/emitModels.d.ts.map +1 -1
  12. package/dist/src/modular/emitModels.js +19 -0
  13. package/dist/src/modular/emitModels.js.map +1 -1
  14. package/dist/src/modular/emitSamples.js +2 -2
  15. package/dist/src/modular/emitSamples.js.map +1 -1
  16. package/dist/src/modular/helpers/clientHelpers.d.ts +2 -1
  17. package/dist/src/modular/helpers/clientHelpers.d.ts.map +1 -1
  18. package/dist/src/modular/helpers/clientHelpers.js +5 -2
  19. package/dist/src/modular/helpers/clientHelpers.js.map +1 -1
  20. package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
  21. package/dist/src/modular/helpers/operationHelpers.js +266 -43
  22. package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
  23. package/dist/src/modular/serialization/buildXmlSerializerFunction.d.ts +32 -0
  24. package/dist/src/modular/serialization/buildXmlSerializerFunction.d.ts.map +1 -0
  25. package/dist/src/modular/serialization/buildXmlSerializerFunction.js +373 -0
  26. package/dist/src/modular/serialization/buildXmlSerializerFunction.js.map +1 -0
  27. package/dist/src/modular/static-helpers-metadata.d.ts +57 -0
  28. package/dist/src/modular/static-helpers-metadata.d.ts.map +1 -1
  29. package/dist/src/modular/static-helpers-metadata.js +57 -0
  30. package/dist/src/modular/static-helpers-metadata.js.map +1 -1
  31. package/dist/src/utils/clientUtils.d.ts.map +1 -1
  32. package/dist/src/utils/clientUtils.js +1 -0
  33. package/dist/src/utils/clientUtils.js.map +1 -1
  34. package/dist/src/utils/mediaTypes.d.ts +4 -0
  35. package/dist/src/utils/mediaTypes.d.ts.map +1 -1
  36. package/dist/src/utils/mediaTypes.js +10 -0
  37. package/dist/src/utils/mediaTypes.js.map +1 -1
  38. package/dist/src/utils/modelUtils.d.ts.map +1 -1
  39. package/dist/src/utils/modelUtils.js +3 -0
  40. package/dist/src/utils/modelUtils.js.map +1 -1
  41. package/dist/src/utils/operationUtil.d.ts +12 -0
  42. package/dist/src/utils/operationUtil.d.ts.map +1 -1
  43. package/dist/src/utils/operationUtil.js +22 -1
  44. package/dist/src/utils/operationUtil.js.map +1 -1
  45. package/dist/tsconfig.tsbuildinfo +1 -1
  46. package/package.json +4 -3
  47. package/src/index.ts +20 -5
  48. package/src/modular/buildClientContext.ts +12 -5
  49. package/src/modular/buildOperations.ts +34 -3
  50. package/src/modular/emitModels.ts +43 -0
  51. package/src/modular/emitSamples.ts +2 -2
  52. package/src/modular/helpers/clientHelpers.ts +6 -2
  53. package/src/modular/helpers/operationHelpers.ts +377 -57
  54. package/src/modular/serialization/buildXmlSerializerFunction.ts +511 -0
  55. package/src/modular/static-helpers-metadata.ts +58 -0
  56. package/src/utils/clientUtils.ts +1 -0
  57. package/src/utils/mediaTypes.ts +12 -0
  58. package/src/utils/modelUtils.ts +3 -0
  59. package/src/utils/operationUtil.ts +34 -1
  60. package/static/static-helpers/serialization/xml-helpers.ts +596 -0
@@ -0,0 +1,596 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ import { XMLBuilder, XMLParser, XmlBuilderOptions } from "fast-xml-parser";
5
+
6
+ /**
7
+ * XML serialization options for a property or model
8
+ */
9
+ export interface XmlSerializationOptions {
10
+ /** Element or attribute name */
11
+ name: string;
12
+ /** If true, serialize as XML attribute instead of element */
13
+ attribute?: boolean;
14
+ /** XML namespace for this element/attribute */
15
+ ns?: {
16
+ namespace: string;
17
+ prefix: string;
18
+ };
19
+ /** For arrays - if true, items are inline without wrapper element */
20
+ unwrapped?: boolean;
21
+ /** For arrays - name of each item element */
22
+ itemsName?: string;
23
+ /** For arrays - namespace for item elements */
24
+ itemsNs?: {
25
+ namespace: string;
26
+ prefix: string;
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Metadata for serializing a model property to XML
32
+ */
33
+ export interface XmlPropertyMetadata {
34
+ /** Client-side property name */
35
+ propertyName: string;
36
+ /** XML serialization options */
37
+ xmlOptions: XmlSerializationOptions;
38
+ /** Serializer function for complex types */
39
+ serializer?: (value: any) => XmlSerializedValue;
40
+ /** Type of the property for special handling */
41
+ type?: "array" | "object" | "primitive" | "date" | "bytes" | "dict";
42
+ /** Date encoding format */
43
+ dateEncoding?: "rfc3339" | "rfc7231" | "unixTimestamp";
44
+ }
45
+
46
+ /**
47
+ * Metadata for deserializing XML to a model property
48
+ */
49
+ export interface XmlPropertyDeserializeMetadata {
50
+ /** Client-side property name */
51
+ propertyName: string;
52
+ /** XML serialization options */
53
+ xmlOptions: XmlSerializationOptions;
54
+ /** Deserializer function for complex types */
55
+ deserializer?: (value: any) => any;
56
+ /** Type of the property for special handling */
57
+ type?: "array" | "object" | "primitive" | "date" | "bytes" | "dict";
58
+ /** Date encoding format */
59
+ dateEncoding?: "rfc3339" | "rfc7231" | "unixTimestamp";
60
+ }
61
+
62
+ /**
63
+ * Result of XML serialization - either a primitive value or structured XML object
64
+ */
65
+ export type XmlSerializedValue =
66
+ | string
67
+ | number
68
+ | boolean
69
+ | null
70
+ | undefined
71
+ | XmlSerializedObject;
72
+
73
+ export interface XmlSerializedObject {
74
+ [key: string]: XmlSerializedValue | XmlSerializedValue[];
75
+ }
76
+
77
+ // Default XML parser/builder options
78
+ const defaultParserOptions = {
79
+ ignoreAttributes: false,
80
+ attributeNamePrefix: "@_",
81
+ textNodeName: "#text",
82
+ parseAttributeValue: true,
83
+ trimValues: false, // Preserve whitespace in text content
84
+ isArray: (
85
+ _name: string,
86
+ _jpath: string,
87
+ isLeafNode: boolean,
88
+ isAttribute: boolean
89
+ ) => {
90
+ // Don't auto-detect arrays for attributes or leaf nodes by default
91
+ // Let the metadata drive array detection
92
+ return !isAttribute && !isLeafNode;
93
+ }
94
+ };
95
+
96
+ const defaultBuilderOptions: Partial<XmlBuilderOptions> = {
97
+ ignoreAttributes: false,
98
+ attributeNamePrefix: "@_",
99
+ textNodeName: "#text",
100
+ format: true,
101
+ suppressEmptyNode: true
102
+ };
103
+
104
+ /**
105
+ * Creates an XML element name with optional namespace prefix
106
+ */
107
+ function getElementName(
108
+ name: string,
109
+ ns?: { namespace: string; prefix: string }
110
+ ): string {
111
+ if (ns?.prefix) {
112
+ return `${ns.prefix}:${name}`;
113
+ }
114
+ return name;
115
+ }
116
+
117
+ /**
118
+ * Creates namespace declaration attributes for the root element
119
+ */
120
+ function createNamespaceDeclarations(
121
+ namespaces: Map<string, string>
122
+ ): Record<string, string> {
123
+ const declarations: Record<string, string> = {};
124
+ for (const [prefix, namespace] of namespaces) {
125
+ if (prefix) {
126
+ declarations[`@_xmlns:${prefix}`] = namespace;
127
+ } else {
128
+ declarations["@_xmlns"] = namespace;
129
+ }
130
+ }
131
+ return declarations;
132
+ }
133
+
134
+ /**
135
+ * Collects all namespaces from property metadata
136
+ */
137
+ function collectNamespaces(
138
+ properties: XmlPropertyMetadata[],
139
+ rootNs?: { namespace: string; prefix: string }
140
+ ): Map<string, string> {
141
+ const namespaces = new Map<string, string>();
142
+
143
+ if (rootNs) {
144
+ namespaces.set(rootNs.prefix, rootNs.namespace);
145
+ }
146
+
147
+ for (const prop of properties) {
148
+ if (prop.xmlOptions.ns) {
149
+ namespaces.set(prop.xmlOptions.ns.prefix, prop.xmlOptions.ns.namespace);
150
+ }
151
+ if (prop.xmlOptions.itemsNs) {
152
+ namespaces.set(
153
+ prop.xmlOptions.itemsNs.prefix,
154
+ prop.xmlOptions.itemsNs.namespace
155
+ );
156
+ }
157
+ }
158
+
159
+ return namespaces;
160
+ }
161
+
162
+ /**
163
+ * Serializes a primitive value for XML
164
+ */
165
+ function serializePrimitiveValue(
166
+ value: any,
167
+ type?: "array" | "object" | "primitive" | "date" | "bytes" | "dict",
168
+ dateEncoding?: "rfc3339" | "rfc7231" | "unixTimestamp"
169
+ ): string | number | boolean {
170
+ if (value === null || value === undefined) {
171
+ return "";
172
+ }
173
+
174
+ if (type === "date" && value instanceof Date) {
175
+ switch (dateEncoding) {
176
+ case "rfc7231":
177
+ return value.toUTCString();
178
+ case "unixTimestamp":
179
+ return Math.floor(value.getTime() / 1000);
180
+ case "rfc3339":
181
+ default:
182
+ return value.toISOString();
183
+ }
184
+ }
185
+
186
+ if (type === "bytes" && value instanceof Uint8Array) {
187
+ // Convert bytes to base64
188
+ return btoa(String.fromCharCode(...value));
189
+ }
190
+
191
+ if (typeof value === "boolean" || typeof value === "number") {
192
+ return value;
193
+ }
194
+
195
+ return String(value);
196
+ }
197
+
198
+ /**
199
+ * Serializes an array property to XML format
200
+ */
201
+ function serializeArrayProperty(
202
+ value: any[],
203
+ metadata: XmlPropertyMetadata
204
+ ): XmlSerializedObject | XmlSerializedValue[] {
205
+ const { xmlOptions, serializer } = metadata;
206
+ const itemName = getElementName(
207
+ xmlOptions.itemsName || xmlOptions.name,
208
+ xmlOptions.itemsNs || xmlOptions.ns
209
+ );
210
+
211
+ const serializedItems = value.map((item) => {
212
+ if (serializer) {
213
+ return serializer(item);
214
+ }
215
+ return serializePrimitiveValue(item, metadata.type, metadata.dateEncoding);
216
+ });
217
+
218
+ if (xmlOptions.unwrapped) {
219
+ // Unwrapped: return items directly (they'll be siblings)
220
+ return serializedItems;
221
+ } else {
222
+ // Wrapped: items are nested under a wrapper element, which contains item elements
223
+ const wrapperName = getElementName(xmlOptions.name, xmlOptions.ns);
224
+ return {
225
+ [wrapperName]: {
226
+ [itemName]: serializedItems
227
+ }
228
+ } as XmlSerializedObject;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Serializes a model to XML object structure
234
+ */
235
+ export function serializeModelToXml(
236
+ item: Record<string, any>,
237
+ properties: XmlPropertyMetadata[],
238
+ rootName: string,
239
+ rootNs?: { namespace: string; prefix: string }
240
+ ): XmlSerializedObject {
241
+ if (item === null || item === undefined) {
242
+ return {};
243
+ }
244
+
245
+ const result: XmlSerializedObject = {};
246
+ const attributes: Record<string, any> = {};
247
+
248
+ // Collect and add namespace declarations
249
+ const namespaces = collectNamespaces(properties, rootNs);
250
+ const nsDeclarations = createNamespaceDeclarations(namespaces);
251
+ Object.assign(attributes, nsDeclarations);
252
+
253
+ for (const prop of properties) {
254
+ const value = item[prop.propertyName];
255
+
256
+ if (value === undefined) {
257
+ continue;
258
+ }
259
+
260
+ const { xmlOptions, serializer, type } = prop;
261
+ const elementName = getElementName(xmlOptions.name, xmlOptions.ns);
262
+
263
+ if (xmlOptions.attribute) {
264
+ // Serialize as attribute
265
+ const attrName = xmlOptions.ns?.prefix
266
+ ? `@_${xmlOptions.ns.prefix}:${xmlOptions.name}`
267
+ : `@_${xmlOptions.name}`;
268
+ attributes[attrName] = serializePrimitiveValue(
269
+ value,
270
+ type,
271
+ prop.dateEncoding
272
+ );
273
+ } else if (type === "dict" && value !== null && typeof value === "object") {
274
+ // Serialize dictionary - each key-value pair becomes an element
275
+ const dictContent: Record<string, XmlSerializedValue> = {};
276
+ for (const [key, val] of Object.entries(value)) {
277
+ dictContent[key] = String(val);
278
+ }
279
+ result[elementName] = dictContent;
280
+ } else if (Array.isArray(value)) {
281
+ // Serialize array
282
+ const arrayResult = serializeArrayProperty(value, prop);
283
+ if (xmlOptions.unwrapped && Array.isArray(arrayResult)) {
284
+ // For unwrapped arrays, add each item as a separate element
285
+ const itemName = getElementName(
286
+ xmlOptions.itemsName || xmlOptions.name,
287
+ xmlOptions.itemsNs || xmlOptions.ns
288
+ );
289
+ result[itemName] = arrayResult as XmlSerializedValue[];
290
+ } else {
291
+ Object.assign(result, arrayResult);
292
+ }
293
+ } else if (value !== null && typeof value === "object" && serializer) {
294
+ // Serialize nested object
295
+ result[elementName] = serializer(value);
296
+ } else if (xmlOptions.unwrapped && !Array.isArray(value)) {
297
+ // Unwrapped primitive - this becomes the text content of the parent element
298
+ result["#text"] = serializePrimitiveValue(value, type, prop.dateEncoding);
299
+ } else {
300
+ // Serialize primitive
301
+ result[elementName] = serializePrimitiveValue(
302
+ value,
303
+ type,
304
+ prop.dateEncoding
305
+ );
306
+ }
307
+ }
308
+
309
+ // Merge attributes into result
310
+ Object.assign(result, attributes);
311
+
312
+ // Wrap in root element
313
+ const rootElementName = getElementName(rootName, rootNs);
314
+ return {
315
+ [rootElementName]: result
316
+ };
317
+ }
318
+
319
+ /**
320
+ * Converts an XML object structure to XML string
321
+ */
322
+ export function xmlObjectToString(
323
+ xmlObject: XmlSerializedObject,
324
+ options?: Partial<XmlBuilderOptions>
325
+ ): string {
326
+ const builder = new XMLBuilder({
327
+ ...defaultBuilderOptions,
328
+ ...options
329
+ });
330
+
331
+ return builder.build(xmlObject);
332
+ }
333
+
334
+ /**
335
+ * Full serialization: model to XML string
336
+ */
337
+ export function serializeToXml(
338
+ item: Record<string, any>,
339
+ properties: XmlPropertyMetadata[],
340
+ rootName: string,
341
+ rootNs?: { namespace: string; prefix: string },
342
+ options?: Partial<XmlBuilderOptions>
343
+ ): string {
344
+ const xmlObject = serializeModelToXml(item, properties, rootName, rootNs);
345
+ return xmlObjectToString(xmlObject, options);
346
+ }
347
+
348
+ /**
349
+ * Parses XML string to object structure
350
+ */
351
+ export function parseXmlString(
352
+ xmlString: string,
353
+ options?: Partial<typeof defaultParserOptions>
354
+ ): any {
355
+ const parser = new XMLParser({
356
+ ...defaultParserOptions,
357
+ ...options
358
+ });
359
+
360
+ return parser.parse(xmlString);
361
+ }
362
+
363
+ /**
364
+ * Deserializes a primitive value from XML
365
+ */
366
+ function deserializePrimitiveValue(
367
+ value: any,
368
+ type?: "array" | "object" | "primitive" | "date" | "bytes" | "dict",
369
+ dateEncoding?: "rfc3339" | "rfc7231" | "unixTimestamp"
370
+ ): any {
371
+ if (value === null || value === undefined || value === "") {
372
+ return undefined;
373
+ }
374
+
375
+ if (type === "date") {
376
+ if (dateEncoding === "unixTimestamp" && typeof value === "number") {
377
+ return new Date(value * 1000);
378
+ }
379
+ return new Date(value);
380
+ }
381
+
382
+ if (type === "bytes" && typeof value === "string") {
383
+ // Convert base64 to bytes
384
+ const binaryString = atob(value);
385
+ const bytes = new Uint8Array(binaryString.length);
386
+ for (let i = 0; i < binaryString.length; i++) {
387
+ bytes[i] = binaryString.charCodeAt(i);
388
+ }
389
+ return bytes;
390
+ }
391
+
392
+ return value;
393
+ }
394
+
395
+ /**
396
+ * Unwraps a value if it's a single-element array (parser wraps non-leaf elements in arrays)
397
+ */
398
+ function unwrapSingleElementArray(value: any): any {
399
+ if (Array.isArray(value) && value.length === 1) {
400
+ return value[0];
401
+ }
402
+ return value;
403
+ }
404
+
405
+ /**
406
+ * Extracts element value from parsed XML, handling namespaces
407
+ */
408
+ function getElementValue(obj: any, xmlOptions: XmlSerializationOptions): any {
409
+ if (!obj) {
410
+ return undefined;
411
+ }
412
+
413
+ const { name, ns, attribute } = xmlOptions;
414
+
415
+ if (attribute) {
416
+ // Look for attribute with or without namespace prefix
417
+ const attrName = ns?.prefix ? `@_${ns.prefix}:${name}` : `@_${name}`;
418
+ return obj[attrName];
419
+ }
420
+
421
+ // Look for element with or without namespace prefix
422
+ const elementName = ns?.prefix ? `${ns.prefix}:${name}` : name;
423
+ const value = obj[elementName] ?? obj[name]; // Fall back to name without prefix
424
+
425
+ // Unwrap single-element arrays (parser wraps non-leaf elements in arrays)
426
+ return unwrapSingleElementArray(value);
427
+ }
428
+
429
+ /**
430
+ * Deserializes an array from XML
431
+ */
432
+ function deserializeArrayProperty(
433
+ obj: any,
434
+ metadata: XmlPropertyDeserializeMetadata
435
+ ): any[] {
436
+ const { xmlOptions, deserializer, type, dateEncoding } = metadata;
437
+
438
+ let arrayData: any;
439
+
440
+ if (xmlOptions.unwrapped) {
441
+ // Items are direct children
442
+ const itemName = getElementName(
443
+ xmlOptions.itemsName || xmlOptions.name,
444
+ xmlOptions.itemsNs || xmlOptions.ns
445
+ );
446
+ arrayData = obj[itemName] ?? obj[xmlOptions.itemsName || xmlOptions.name];
447
+ } else {
448
+ // Items are nested under wrapper element
449
+ const wrapperName = getElementName(xmlOptions.name, xmlOptions.ns);
450
+ let wrapper = obj[wrapperName] ?? obj[xmlOptions.name];
451
+
452
+ if (!wrapper) {
453
+ return [];
454
+ }
455
+
456
+ // Unwrap single-element array if the parser wrapped it
457
+ wrapper = unwrapSingleElementArray(wrapper);
458
+
459
+ const itemName = getElementName(
460
+ xmlOptions.itemsName || xmlOptions.name,
461
+ xmlOptions.itemsNs
462
+ );
463
+ arrayData =
464
+ wrapper[itemName] ??
465
+ wrapper[xmlOptions.itemsName || xmlOptions.name] ??
466
+ wrapper;
467
+ }
468
+
469
+ if (!arrayData) {
470
+ return [];
471
+ }
472
+
473
+ // Ensure it's an array
474
+ const items = Array.isArray(arrayData) ? arrayData : [arrayData];
475
+
476
+ return items.map((item) => {
477
+ // Unwrap single-element array for each item if needed
478
+ const unwrappedItem = unwrapSingleElementArray(item);
479
+ if (deserializer) {
480
+ return deserializer(unwrappedItem);
481
+ }
482
+ return deserializePrimitiveValue(unwrappedItem, type, dateEncoding);
483
+ });
484
+ }
485
+
486
+ /**
487
+ * Deserializes XML object structure to model
488
+ */
489
+ export function deserializeXmlToModel<T = Record<string, any>>(
490
+ xmlObject: any,
491
+ properties: XmlPropertyDeserializeMetadata[],
492
+ rootName: string,
493
+ rootNs?: { namespace: string; prefix: string }
494
+ ): T {
495
+ if (!xmlObject) {
496
+ return {} as T;
497
+ }
498
+
499
+ // Get root element content - unwrap if it's wrapped in an array by the parser
500
+ const rootElementName = getElementName(rootName, rootNs);
501
+ let content = xmlObject[rootElementName] ?? xmlObject[rootName] ?? xmlObject;
502
+ content = unwrapSingleElementArray(content);
503
+
504
+ const result: Record<string, any> = {};
505
+
506
+ for (const prop of properties) {
507
+ const { propertyName, xmlOptions, deserializer, type, dateEncoding } = prop;
508
+
509
+ if (type === "array" || xmlOptions.itemsName) {
510
+ // Deserialize array
511
+ result[propertyName] = deserializeArrayProperty(content, prop);
512
+ } else if (type === "dict") {
513
+ // Deserialize dictionary - each child element is a key-value pair
514
+ const rawValue = getElementValue(content, xmlOptions);
515
+ if (rawValue !== undefined && typeof rawValue === "object") {
516
+ const dict: Record<string, string> = {};
517
+ for (const [key, val] of Object.entries(rawValue)) {
518
+ // Skip attributes (start with @_) and text nodes (#text)
519
+ if (!key.startsWith("@_") && key !== "#text") {
520
+ dict[key] = String(val);
521
+ }
522
+ }
523
+ result[propertyName] = dict;
524
+ }
525
+ } else if (xmlOptions.unwrapped && type !== "object") {
526
+ // Unwrapped primitive - get text content from the element
527
+ const rawValue = content["#text"];
528
+ if (rawValue !== undefined) {
529
+ result[propertyName] = deserializePrimitiveValue(
530
+ rawValue,
531
+ type,
532
+ dateEncoding
533
+ );
534
+ }
535
+ } else {
536
+ // Get element or attribute value
537
+ const rawValue = getElementValue(content, xmlOptions);
538
+
539
+ if (rawValue === undefined) {
540
+ continue;
541
+ }
542
+
543
+ if (deserializer && typeof rawValue === "object") {
544
+ // Deserialize nested object
545
+ result[propertyName] = deserializer(rawValue);
546
+ } else {
547
+ // Deserialize primitive
548
+ result[propertyName] = deserializePrimitiveValue(
549
+ rawValue,
550
+ type,
551
+ dateEncoding
552
+ );
553
+ }
554
+ }
555
+ }
556
+
557
+ return result as T;
558
+ }
559
+
560
+ /**
561
+ * Full deserialization: XML string to model
562
+ */
563
+ export function deserializeFromXml<T = Record<string, any>>(
564
+ xmlString: string,
565
+ properties: XmlPropertyDeserializeMetadata[],
566
+ rootName: string,
567
+ rootNs?: { namespace: string; prefix: string },
568
+ parserOptions?: Partial<typeof defaultParserOptions>
569
+ ): T {
570
+ const xmlObject = parseXmlString(xmlString, parserOptions);
571
+ return deserializeXmlToModel<T>(xmlObject, properties, rootName, rootNs);
572
+ }
573
+
574
+ /**
575
+ * Utility to check if a content type is XML
576
+ */
577
+ export function isXmlContentType(contentType: string): boolean {
578
+ const normalized = contentType.toLowerCase();
579
+ return (
580
+ normalized.includes("application/xml") ||
581
+ normalized.includes("text/xml") ||
582
+ normalized.endsWith("+xml")
583
+ );
584
+ }
585
+
586
+ /**
587
+ * Utility to check if a content type is JSON
588
+ */
589
+ export function isJsonContentType(contentType: string): boolean {
590
+ const normalized = contentType.toLowerCase();
591
+ return (
592
+ normalized.includes("application/json") ||
593
+ normalized.includes("text/json") ||
594
+ normalized.endsWith("+json")
595
+ );
596
+ }