projects/wms-framework/src/lib/baseframework/XmlSerializer.ts
Serialize and deserialize from an XML stream.
Methods |
|
constructor(type: RuntimeTypeInfo)
|
||||||
Creates an instance of XmlSerializer.
Parameters :
|
Deserialize | ||||||
Deserialize(input: XmlReader | TextReader | SmStream)
|
||||||
Deserialize an XML stream.
Parameters :
Returns :
any
{*} |
Private deserializeCollectionProperty | ||||||||||||
deserializeCollectionProperty(obj: any, propInfo: PropertyInfo, input: Element)
|
||||||||||||
Parameters :
Returns :
void
|
Private deserializeObjectProperties | ||||||||||||
deserializeObjectProperties(elements: HTMLCollection, typeProps: PropertyInfo[], obj: any)
|
||||||||||||
Parameters :
Returns :
void
|
Private deserializePropertyValue | ||||||||||||
deserializePropertyValue(input: Element, propInfo: PropertyInfo, obj: any)
|
||||||||||||
Convert the input string to the given property's type.
Parameters :
Returns :
void
{*} |
Private deserializeSingleValue | ||||||||||||
deserializeSingleValue(propInfo: PropertyInfo, obj: any, input: Element)
|
||||||||||||
Parameters :
Returns :
void
|
Private deserializeUsingSingleElementStrategy | ||||||||||||||||||
deserializeUsingSingleElementStrategy(allowedTypeInfo: AllowedCollectionTypeInformation, input: Element, targetCollection: any, propInfo: PropertyInfo, obj: any)
|
||||||||||||||||||
Parameters :
Returns :
void
|
Private deserializeUsingTypeInheritanceStrategy | ||||||||||||||||||
deserializeUsingTypeInheritanceStrategy(input: Element, allowedTypeInfo: AllowedCollectionTypeInformation, targetCollection: any, propInfo: PropertyInfo, obj: any)
|
||||||||||||||||||
Parameters :
Returns :
void
|
Private getTargetCollection | |||||||||
getTargetCollection(obj: any, propInfo: PropertyInfo)
|
|||||||||
Parameters :
Returns :
any
|
Serialize | |||||||||
Serialize(writer: unknown, obj: any)
|
|||||||||
Serialize the given object into an XML stream.
Parameters :
Returns :
void
|
SerializeObjectOnWriter |
SerializeObjectOnWriter(xmlWriter: XmlWriter, obj: any)
|
Returns :
void
|
Private WriteCollectionProperty | |||||||||||||||
WriteCollectionProperty(obj: any, prop: PropertyInfo, value: any, xmlWriter: XmlWriter)
|
|||||||||||||||
Parameters :
Returns :
void
|
Private WriteProperties | ||||||||||||
WriteProperties(properties: PropertyInfo[], obj: any, xmlWriter: XmlWriter)
|
||||||||||||
Parameters :
Returns :
void
|
Private WriteSingleValue | ||||||||||||
WriteSingleValue(value: any, xmlWriter: XmlWriter, prop: PropertyInfo)
|
||||||||||||
Parameters :
Returns :
void
|
import { Debugger } from '../diagnostics/Debugger';
import { SmStream, TextWriter } from '../helpers/StreamsSupport';
import { MetadataAttribute } from './MetadataAttribute';
import {
ExtendedPropertyTypeInfo,
PropertyInfo,
ReflectionHelper,
RuntimeTypeInfo,
} from './ReflectionSupport';
import { StringReader } from './StringReader';
import { TextReader } from './TextReader';
import { XmlReader } from './xmlsupport';
import { XmlWriter } from './XmlWriter';
import { SimpleList } from './collections';
/**
* Serialize and deserialize from an XML stream.
*
* @export
* @class XmlSerializer
* @wType System.Xml.Serialization.XmlSerializer
* @wNetSupport
*/
export class XmlSerializer {
/**
* Creates an instance of XmlSerializer.
*
* @param {RuntimeTypeInfo} type
* @memberof XmlSerializer
*/
constructor(private type: RuntimeTypeInfo) {}
/**
* Serialize the given object into an XML stream.
*
* @param {*} writer
* @param {*} obj
* @memberof XmlSerializer
*/
Serialize(writer: unknown, obj: any) {
let xmlWriter: XmlWriter = null;
if (writer instanceof TextWriter) {
xmlWriter = XmlWriter.Create(writer);
} else if (writer instanceof XmlWriter) {
xmlWriter = writer;
} else {
Debugger.Throw('Not implemented');
}
if (xmlWriter) {
this.SerializeObjectOnWriter(xmlWriter, obj);
}
}
/**
*
* @param {XmlWriter} xmlWriter
* @param {*} obj
* @wIgnore
*/
SerializeObjectOnWriter(xmlWriter: XmlWriter, obj: any) {
const properties = this.type.getProperties(true);
const objClassName = obj.constructor.name;
xmlWriter.WriteStartDocument();
xmlWriter.WriteStartElement(objClassName);
xmlWriter.WriteAttributeString(
'xmlns:xsi',
'http://www.w3.org/2001/XMLSchema-instance'
);
xmlWriter.WriteAttributeString(
'xmlns:xsd',
'http://www.w3.org/2001/XMLSchema-instance'
);
this.WriteProperties(properties, obj, xmlWriter);
xmlWriter.WriteEndElement();
xmlWriter.WriteEndDocument();
}
private WriteProperties(
properties: PropertyInfo[],
obj: any,
xmlWriter: XmlWriter
) {
for (const prop of properties) {
const value = prop.getValue(obj);
if (prop.canRead && value != null) {
xmlWriter.WriteStartElement(prop.Name);
if (value && value[Symbol.iterator] && typeof value !== 'string') {
this.WriteCollectionProperty(obj, prop, value, xmlWriter);
} else {
this.WriteSingleValue(value, xmlWriter, prop);
}
xmlWriter.WriteEndElement();
}
}
}
private WriteSingleValue(
value: any,
xmlWriter: XmlWriter,
prop: PropertyInfo
) {
if (value instanceof Date) {
xmlWriter.WriteValue(value.toISOString());
} else if (typeof value === 'object') {
if (prop.propertyType) {
this.WriteProperties(
prop.propertyType.getProperties(),
value,
xmlWriter
);
} else {
console.warn('Cannot write type of unknown type' + value);
}
} else {
xmlWriter.WriteValue(value);
}
}
private WriteCollectionProperty(
obj: any,
prop: PropertyInfo,
value: any,
xmlWriter: XmlWriter
) {
const collectionTypeInfo = extractAllowedTypeInformation(obj, prop);
if (collectionTypeInfo.typesWereFound) {
for (const entry of value) {
if (entry && entry.constructor?.name in collectionTypeInfo.foundTypes) {
xmlWriter.WriteStartElement(collectionTypeInfo.mainType.Name);
xmlWriter.WriteAttributeString('xsi:type', entry.constructor?.name);
const specificRtti =
collectionTypeInfo.foundTypes[entry.constructor.name];
this.WriteProperties(specificRtti.getProperties(), entry, xmlWriter);
xmlWriter.WriteEndElement();
}
}
} else if (collectionTypeInfo.mainType) {
for (const entry of value) {
if (
collectionTypeInfo.mainType.Name in PredefinedTypeDeserializationInfo
) {
const predefinedInfo =
PredefinedTypeDeserializationInfo[collectionTypeInfo.mainType.Name];
xmlWriter.WriteStartElement(predefinedInfo.wrapperElementName);
xmlWriter.WriteValue(entry);
xmlWriter.WriteEndElement();
} else {
xmlWriter.WriteStartElement(collectionTypeInfo.mainType.Name);
this.WriteProperties(
collectionTypeInfo.mainType.getProperties(),
entry,
xmlWriter
);
xmlWriter.WriteEndElement();
}
}
}
}
/**
* Deserialize an XML stream.
*
* @param {(XmlReader | TextReader | SmStream)} input
* @return {*} {*}
* @memberof XmlSerializer
*/
Deserialize(input: XmlReader | TextReader | SmStream): any {
if (!(input instanceof StringReader)) {
Debugger.Throw(
'XmlSerializer.Deserialize() only supports StringReader instances as input.'
);
}
const doc = new DOMParser().parseFromString(
(input as StringReader).text,
'application/xml'
);
if (doc.documentElement.nodeName !== this.type.Name) {
throw new Error(
`XML document error: top level element (${doc.documentElement.nodeName}) does not match expected type (${this.type.Name}).`
);
}
const typeProps = this.type.getProperties();
const obj = new this.type.JSType();
this.deserializeObjectProperties(
doc.documentElement.children,
typeProps,
obj
);
return obj;
}
private deserializeObjectProperties(
elements: HTMLCollection,
typeProps: PropertyInfo[],
obj: any
) {
for (const el of elements) {
const propInfo = typeProps.find((x) => x.Name === el.nodeName);
if (propInfo != null) {
if (propInfo.propertyType) {
this.deserializePropertyValue(el, propInfo, obj);
} else {
console.warn(`Cannot deserialize property: ${propInfo.Name}`);
}
}
}
}
/**
* Convert the input string to the given property's type.
*
* @private
* @param {string} input
* @param {PropertyInfo} propInfo
* @return {*} {*}
* @memberof XmlSerializer
*/
private deserializePropertyValue(
input: Element,
propInfo: PropertyInfo,
obj: any
) {
const isList =
propInfo.propertyType &&
ReflectionHelper.getTypeInfo(
ReflectionHelper.getInterfaceRuntimeTypeInfo(
'System.Collections.Generic.IList`1'
)
).IsAssignableFrom(propInfo.propertyType);
if (isList) {
this.deserializeCollectionProperty(obj, propInfo, input);
} else if (propInfo.canWrite) {
this.deserializeSingleValue(propInfo, obj, input);
}
}
private deserializeSingleValue(
propInfo: PropertyInfo,
obj: any,
input: Element
) {
if (input.getAttribute('xsi:nil') === 'true') {
obj[propInfo.Name] = null;
} else {
const deserializationInfo =
PredefinedTypeDeserializationInfo[propInfo.propertyType.Name];
if (deserializationInfo) {
obj[propInfo.Name] = deserializationInfo.deserializationFunction(
input.innerHTML
);
} else {
const newInstance = new propInfo.propertyType.JSType();
obj[propInfo.Name] = newInstance;
this.deserializeObjectProperties(
input.children,
propInfo.propertyType.getProperties(),
newInstance
);
}
}
}
/* istanbul ignore next */
private getTargetCollection(obj: any, propInfo: PropertyInfo): any {
let propType = null;
if (propInfo.propertyType) {
propType = propInfo.propertyType.JSType
? new propInfo.propertyType.JSType()
: new SimpleList<any>();
}
return propInfo.canWrite ? propType : propInfo.getValue(obj);
}
private deserializeCollectionProperty(
obj: any,
propInfo: PropertyInfo,
input: Element
) {
const allowedTypeInfo = extractAllowedTypeInformation(obj, propInfo);
const targetCollection = this.getTargetCollection(obj, propInfo);
if (allowedTypeInfo.typesWereFound) {
this.deserializeUsingTypeInheritanceStrategy(
input,
allowedTypeInfo,
targetCollection,
propInfo,
obj
);
} else if (allowedTypeInfo.mainType) {
this.deserializeUsingSingleElementStrategy(
allowedTypeInfo,
input,
targetCollection,
propInfo,
obj
);
}
}
private deserializeUsingSingleElementStrategy(
allowedTypeInfo: AllowedCollectionTypeInformation,
input: Element,
targetCollection: any,
propInfo: PropertyInfo,
obj: any
) {
if (allowedTypeInfo.mainType.Name in PredefinedTypeDeserializationInfo) {
const deserializationInfo =
PredefinedTypeDeserializationInfo[allowedTypeInfo.mainType.Name];
for (const child of input.children) {
if (
child instanceof Element &&
child.tagName === deserializationInfo.wrapperElementName
) {
targetCollection.add(
deserializationInfo.deserializationFunction(child.innerHTML)
);
}
}
} else {
for (const child of input.children) {
if (
child instanceof Element &&
child.tagName === allowedTypeInfo.mainType.Name
) {
const newElement = new allowedTypeInfo.mainType.JSType();
this.deserializeObjectProperties(
child.children,
allowedTypeInfo.mainType.getProperties(),
newElement
);
targetCollection.add(newElement);
}
}
}
// if the property is not writable we are using the existing value
if (propInfo.canWrite) {
obj[propInfo.Name] = targetCollection;
}
}
private deserializeUsingTypeInheritanceStrategy(
input: Element,
allowedTypeInfo: AllowedCollectionTypeInformation,
targetCollection: any,
propInfo: PropertyInfo,
obj: any
) {
for (const child of input.children) {
if (
child instanceof Element &&
child.tagName === allowedTypeInfo.mainType.Name &&
child.getAttribute('xsi:type') in allowedTypeInfo.foundTypes
) {
const runtimeType =
allowedTypeInfo.foundTypes[child.getAttribute('xsi:type')];
const newInstance = new runtimeType.JSType();
this.deserializeObjectProperties(
child.children,
runtimeType.getProperties(),
newInstance
);
targetCollection.add(newInstance);
}
}
// if the property is not writable we are using the existing value
if (propInfo.canWrite) {
obj[propInfo.Name] = targetCollection;
}
}
}
type PredefinedTypeDeserializationInfos = {
[name: string]: {
wrapperElementName: string;
deserializationFunction: (data: string) => any;
};
};
const PredefinedTypeDeserializationInfo: PredefinedTypeDeserializationInfos = {
Number: {
wrapperElementName: 'int',
deserializationFunction: (data: string) => Number.parseFloat(data),
},
String: {
wrapperElementName: 'string',
deserializationFunction: (data: string) => data,
},
Date: {
wrapperElementName: 'dateTime',
deserializationFunction: (data: string) => new Date(data),
},
Boolean: {
wrapperElementName: 'boolean',
deserializationFunction: (data: string) => data.toLowerCase() === 'true',
},
};
/**
* @wType System.Xml.Serialization.XmlIncludeAttribute
* @wNetSupport
*/
export class XmlInclude extends MetadataAttribute {
constructor(public fullTypeName: string) {
super();
}
}
type AllowedCollectionTypeInformation = {
typesWereFound: boolean;
mainType: RuntimeTypeInfo;
foundTypes: {
[name: string]: RuntimeTypeInfo;
};
};
function extractAllowedTypeInformation(
obj: any,
prop: PropertyInfo
): AllowedCollectionTypeInformation {
const result = { typesWereFound: false, mainType: null, foundTypes: null };
const extendedInfo = Reflect.getMetadata(
'original:extendedPropertyTypeInfo',
obj.constructor.prototype,
prop.Name
) as ExtendedPropertyTypeInfo;
if (extendedInfo?.genericParameterTypeIds?.length > 0) {
const typeId = extendedInfo.genericParameterTypeIds[0];
const typeInformation =
typeof typeId === 'string'
? RuntimeTypeInfo.GetType(typeId)
: ReflectionHelper.getTypeInfo(typeId);
if (typeInformation) {
collectExpectedTypeInfo(result, typeInformation);
}
}
return result;
}
function collectExpectedTypeInfo(
result: { typesWereFound: boolean; mainType: any; foundTypes: any },
typeInformation: RuntimeTypeInfo
) {
result.foundTypes = {};
result.mainType = typeInformation;
const atts = typeInformation.getMetadataAttributes();
for (const att of atts) {
if (att instanceof XmlInclude) {
const runtimeType = RuntimeTypeInfo.GetType(att.fullTypeName);
if (runtimeType) {
result.foundTypes[runtimeType.Name] = runtimeType;
result.typesWereFound = true;
}
}
}
}