File

projects/wms-framework/src/lib/baseframework/XmlSerializer.ts

Description

Serialize and deserialize from an XML stream.

Index

Methods

Constructor

constructor(type: RuntimeTypeInfo)

Creates an instance of XmlSerializer.

Parameters :
Name Type Optional
type RuntimeTypeInfo No

Methods

Deserialize
Deserialize(input: XmlReader | TextReader | SmStream)

Deserialize an XML stream.

Parameters :
Name Type Optional
input XmlReader | TextReader | SmStream No
Returns : any

{*}

Private deserializeCollectionProperty
deserializeCollectionProperty(obj: any, propInfo: PropertyInfo, input: Element)
Parameters :
Name Type Optional
obj any No
propInfo PropertyInfo No
input Element No
Returns : void
Private deserializeObjectProperties
deserializeObjectProperties(elements: HTMLCollection, typeProps: PropertyInfo[], obj: any)
Parameters :
Name Type Optional
elements HTMLCollection No
typeProps PropertyInfo[] No
obj any No
Returns : void
Private deserializePropertyValue
deserializePropertyValue(input: Element, propInfo: PropertyInfo, obj: any)

Convert the input string to the given property's type.

Parameters :
Name Type Optional
input Element No
propInfo PropertyInfo No
obj any No
Returns : void

{*}

Private deserializeSingleValue
deserializeSingleValue(propInfo: PropertyInfo, obj: any, input: Element)
Parameters :
Name Type Optional
propInfo PropertyInfo No
obj any No
input Element No
Returns : void
Private deserializeUsingSingleElementStrategy
deserializeUsingSingleElementStrategy(allowedTypeInfo: AllowedCollectionTypeInformation, input: Element, targetCollection: any, propInfo: PropertyInfo, obj: any)
Parameters :
Name Type Optional
allowedTypeInfo AllowedCollectionTypeInformation No
input Element No
targetCollection any No
propInfo PropertyInfo No
obj any No
Returns : void
Private deserializeUsingTypeInheritanceStrategy
deserializeUsingTypeInheritanceStrategy(input: Element, allowedTypeInfo: AllowedCollectionTypeInformation, targetCollection: any, propInfo: PropertyInfo, obj: any)
Parameters :
Name Type Optional
input Element No
allowedTypeInfo AllowedCollectionTypeInformation No
targetCollection any No
propInfo PropertyInfo No
obj any No
Returns : void
Private getTargetCollection
getTargetCollection(obj: any, propInfo: PropertyInfo)
Parameters :
Name Type Optional
obj any No
propInfo PropertyInfo No
Returns : any
Serialize
Serialize(writer: unknown, obj: any)

Serialize the given object into an XML stream.

Parameters :
Name Type Optional
writer unknown No
obj any No
Returns : void
SerializeObjectOnWriter
SerializeObjectOnWriter(xmlWriter: XmlWriter, obj: any)
Parameters :
Name Type Optional
xmlWriter XmlWriter No
obj any No
Returns : void
Private WriteCollectionProperty
WriteCollectionProperty(obj: any, prop: PropertyInfo, value: any, xmlWriter: XmlWriter)
Parameters :
Name Type Optional
obj any No
prop PropertyInfo No
value any No
xmlWriter XmlWriter No
Returns : void
Private WriteProperties
WriteProperties(properties: PropertyInfo[], obj: any, xmlWriter: XmlWriter)
Parameters :
Name Type Optional
properties PropertyInfo[] No
obj any No
xmlWriter XmlWriter No
Returns : void
Private WriteSingleValue
WriteSingleValue(value: any, xmlWriter: XmlWriter, prop: PropertyInfo)
Parameters :
Name Type Optional
value any No
xmlWriter XmlWriter No
prop PropertyInfo No
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;
      }
    }
  }
}

result-matching ""

    No results matching ""