Web Data Serialization TypeScript Samples

Web TypeScript data serialization examples including JSON serialization/deserialization and XML parsing

💻 JSON Serialization typescript

🟢 simple ⭐⭐⭐

Convert JavaScript objects to JSON strings with formatting, custom replacers, and circular reference handling

⏱️ 20 min 🏷️ typescript, web, serialization, json
Prerequisites: Basic TypeScript, JSON API
// Web TypeScript JSON Serialization Examples
// Converting JavaScript objects to JSON strings with various options

// 1. Basic JSON Serialization
class JSONSerializer {
  // Serialize to JSON string
  stringify(obj: any, pretty: boolean = false): string {
    if (pretty) {
      return JSON.stringify(obj, null, 2);
    }
    return JSON.stringify(obj);
  }

  // Serialize with custom replacer
  stringifyWithReplacer(obj: any, replacer: (key: string, value: any) => any): string {
    return JSON.stringify(obj, replacer, 2);
  }

  // Serialize with date handling
  stringifyWithDates(obj: any): string {
    return JSON.stringify(obj, (key, value) => {
      if (value instanceof Date) {
        return { __type: 'Date', value: value.toISOString() };
      }
      return value;
    }, 2);
  }

  // Serialize with undefined handling
  stringifyWithUndefined(obj: any): string {
    return JSON.stringify(obj, (key, value) => {
      if (value === undefined) {
        return null;
      }
      return value;
    }, 2);
  }

  // Serialize with function handling
  stringifyWithFunctions(obj: any): string {
    return JSON.stringify(obj, (key, value) => {
      if (typeof value === 'function') {
        return value.toString();
      }
      return value;
    }, 2);
  }

  // Serialize with symbol handling
  stringifyWithSymbols(obj: any): string {
    return JSON.stringify(obj, (key, value) => {
      if (typeof value === 'symbol') {
        return value.toString();
      }
      return value;
    }, 2);
  }

  // Serialize with bigint handling
  stringifyWithBigInt(obj: any): string {
    return JSON.stringify(obj, (key, value) => {
      if (typeof value === 'bigint') {
        return value.toString() + 'n';
      }
      return value;
    }, 2);
  }
}

// 2. Circular Reference Handler
class CircularReferenceHandler {
  private seen = new WeakSet();

  // Serialize with circular reference detection
  stringify(obj: any): string {
    this.seen = new WeakSet();
    return JSON.stringify(obj, (key, value) => {
      if (typeof value === 'object' && value !== null) {
        if (this.seen.has(value)) {
          return '[Circular]';
        }
        this.seen.add(value);
      }
      return value;
    }, 2);
  }

  // Serialize with circular reference path tracking
  stringifyWithPath(obj: any): string {
    const paths = new Map<any, string>();
    let counter = 0;

    return JSON.stringify(obj, (key, value) => {
      if (typeof value === 'object' && value !== null) {
        if (paths.has(value)) {
          return { $ref: paths.get(value) };
        }
        const path = `#${counter++}`;
        paths.set(value, path);
        return value;
      }
      return value;
    }, 2);
  }

  // Serialize with circular reference replacement
  stringifyWithReplacement(obj: any, replacement: any = null): string {
    const seen = new WeakSet();

    return JSON.stringify(obj, (key, value) => {
      if (typeof value === 'object' && value !== null) {
        if (seen.has(value)) {
          return replacement;
        }
        seen.add(value);
      }
      return value;
    }, 2);
  }
}

// 3. Custom Type Serialization
class CustomTypeSerializer {
  // Serialize with type metadata
  serializeWithType(obj: any): string {
    return JSON.stringify(obj, (key, value) => {
      if (value instanceof Map) {
        return { __type: 'Map', value: Array.from(value.entries()) };
      }
      if (value instanceof Set) {
        return { __type: 'Set', value: Array.from(value) };
      }
      if (value instanceof Date) {
        return { __type: 'Date', value: value.toISOString() };
      }
      if (value instanceof RegExp) {
        return { __type: 'RegExp', value: { source: value.source, flags: value.flags } };
      }
      if (value instanceof Error) {
        return { __type: 'Error', value: { name: value.name, message: value.message, stack: value.stack } };
      }
      if (typeof value === 'bigint') {
        return { __type: 'BigInt', value: value.toString() };
      }
      return value;
    }, 2);
  }

  // Serialize class instances
  serializeClassInstance(obj: any): string {
    return JSON.stringify(obj, (key, value) => {
      if (value && typeof value === 'object' && value.constructor.name !== 'Object') {
        return {
          __class: value.constructor.name,
          ...value
        };
      }
      return value;
    }, 2);
  }

  // Serialize with custom type converters
  serializeWithConverters(obj: any, converters: Map<string, (value: any) => any>): string {
    return JSON.stringify(obj, (key, value) => {
      for (const [type, converter] of converters) {
        if (typeof value === type || value?.constructor?.name === type) {
          return converter(value);
        }
      }
      return value;
    }, 2);
  }
}

// 4. Compact JSON Serializer
class CompactSerializer {
  // Minify JSON (remove whitespace)
  minify(obj: any): string {
    return JSON.stringify(obj);
  }

  // Compress property names
  compressProperties(obj: any, mapping: Record<string, string>): string {
    const compressed: any = Array.isArray(obj) ? [] : {};

    for (const key in obj) {
      const newKey = mapping[key] || key;
      compressed[newKey] = obj[key];
    }

    return JSON.stringify(compressed);
  }

  // Remove null and undefined values
  removeEmpty(obj: any): string {
    const cleaned = this.removeEmptyValues(obj);
    return JSON.stringify(cleaned);
  }

  private removeEmptyValues(obj: any): any {
    if (obj === null || obj === undefined) {
      return null;
    }

    if (Array.isArray(obj)) {
      return obj.map(v => this.removeEmptyValues(v)).filter(v => v !== null);
    }

    if (typeof obj === 'object') {
      const result: any = {};
      for (const key in obj) {
        const value = this.removeEmptyValues(obj[key]);
        if (value !== null && value !== undefined) {
          result[key] = value;
        }
      }
      return result;
    }

    return obj;
  }

  // Sort keys for consistent output
  sortKeys(obj: any): string {
    const sorted = this.sortObjectKeys(obj);
    return JSON.stringify(sorted, null, 2);
  }

  private sortObjectKeys(obj: any): any {
    if (obj === null || typeof obj !== 'object') {
      return obj;
    }

    if (Array.isArray(obj)) {
      return obj.map(v => this.sortObjectKeys(v));
    }

    const result: any = {};
    const keys = Object.keys(obj).sort();

    for (const key of keys) {
      result[key] = this.sortObjectKeys(obj[key]);
    }

    return result;
  }
}

// 5. Stream Serializer (for large objects)
class StreamSerializer {
  // Chunk-based serialization
  async serializeInChunks(obj: any, chunkSize: number = 1000): Promise<string> {
    const json = JSON.stringify(obj);
    const chunks: string[] = [];

    for (let i = 0; i < json.length; i += chunkSize) {
      chunks.push(json.slice(i, i + chunkSize));
    }

    return chunks.join('');
  }

  // Progressive serialization
  async* serializeProgressively(obj: any): AsyncGenerator<string> {
    const json = JSON.stringify(obj);
    const chunkSize = 1000;

    for (let i = 0; i < json.length; i += chunkSize) {
      yield json.slice(i, Math.min(i + chunkSize, json.length));
    }
  }
}

// 6. Validation Serializer
class ValidationSerializer {
  // Serialize with schema validation
  serializeWithValidation(obj: any, schema: any): string {
    this.validate(obj, schema);
    return JSON.stringify(obj, null, 2);
  }

  private validate(obj: any, schema: any): void {
    if (schema.type === 'object') {
      if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
        throw new Error('Expected object');
      }
      if (schema.properties) {
        for (const key in schema.properties) {
          this.validate(obj[key], schema.properties[key]);
        }
      }
    } else if (schema.type === 'array') {
      if (!Array.isArray(obj)) {
        throw new Error('Expected array');
      }
      if (schema.items) {
        obj.forEach((item: any) => this.validate(item, schema.items));
      }
    } else if (schema.type === 'string') {
      if (typeof obj !== 'string') {
        throw new Error('Expected string');
      }
    } else if (schema.type === 'number') {
      if (typeof obj !== 'number') {
        throw new Error('Expected number');
      }
    } else if (schema.type === 'boolean') {
      if (typeof obj !== 'boolean') {
        throw new Error('Expected boolean');
      }
    }
  }

  // Serialize with size limit
  serializeWithLimit(obj: any, maxSize: number): string {
    const json = JSON.stringify(obj);
    if (json.length > maxSize) {
      throw new Error(`JSON size ${json.length} exceeds limit ${maxSize}`);
    }
    return json;
  }
}

// 7. Filtered Serializer
class FilteredSerializer {
  // Serialize with property filtering
  serializeWithFilter(obj: any, include: string[]): string {
    const filtered = this.filterProperties(obj, include);
    return JSON.stringify(filtered, null, 2);
  }

  // Serialize with property exclusion
  serializeWithExclusion(obj: any, exclude: string[]): string {
    const filtered = this.excludeProperties(obj, exclude);
    return JSON.stringify(filtered, null, 2);
  }

  // Serialize with depth limit
  serializeWithDepth(obj: any, maxDepth: number, currentDepth: number = 0): string {
    if (currentDepth >= maxDepth) {
      return JSON.stringify(obj);
    }

    return JSON.stringify(obj, (key, value) => {
      if (typeof value === 'object' && value !== null) {
        if (Array.isArray(value)) {
          return value.map(v => this.parseValue(v, maxDepth, currentDepth + 1));
        }
        const result: any = {};
        for (const k in value) {
          result[k] = this.parseValue(value[k], maxDepth, currentDepth + 1);
        }
        return result;
      }
      return value;
    }, 2);
  }

  private filterProperties(obj: any, include: string[]): any {
    if (typeof obj !== 'object' || obj === null) {
      return obj;
    }

    if (Array.isArray(obj)) {
      return obj.map(v => this.filterProperties(v, include));
    }

    const result: any = {};
    for (const key of include) {
      if (key in obj) {
        result[key] = this.filterProperties(obj[key], include);
      }
    }
    return result;
  }

  private excludeProperties(obj: any, exclude: string[]): any {
    if (typeof obj !== 'object' || obj === null) {
      return obj;
    }

    if (Array.isArray(obj)) {
      return obj.map(v => this.excludeProperties(v, exclude));
    }

    const result: any = {};
    for (const key in obj) {
      if (!exclude.includes(key)) {
        result[key] = this.excludeProperties(obj[key], exclude);
      }
    }
    return result;
  }

  private parseValue(value: any, maxDepth: number, currentDepth: number): any {
    if (currentDepth >= maxDepth) {
      return typeof value === 'object' ? '[Max Depth Reached]' : value;
    }

    if (typeof value === 'object' && value !== null) {
      if (Array.isArray(value)) {
        return value.map(v => this.parseValue(v, maxDepth, currentDepth + 1));
      }
      const result: any = {};
      for (const k in value) {
        result[k] = this.parseValue(value[k], maxDepth, currentDepth + 1);
      }
      return result;
    }

    return value;
  }
}

// Usage Examples
async function demonstrateJSONSerialization() {
  console.log('=== Web TypeScript JSON Serialization Examples ===\n');

  const serializer = new JSONSerializer();
  const circularHandler = new CircularReferenceHandler();
  const customSerializer = new CustomTypeSerializer();
  const compactSerializer = new CompactSerializer();
  const filteredSerializer = new FilteredSerializer();

  // 1. Basic serialization
  console.log('--- 1. Basic Serialization ---');
  const obj1 = { name: 'Alice', age: 30, city: 'New York' };
  console.log(serializer.stringify(obj1));
  console.log(serializer.stringify(obj1, true));

  // 2. Custom types
  console.log('\n--- 2. Custom Type Serialization ---');
  const obj2 = {
    date: new Date(),
    map: new Map([['key', 'value']]),
    set: new Set([1, 2, 3]),
    regex: /test/g
  };
  console.log(customSerializer.serializeWithType(obj2));

  // 3. Circular references
  console.log('\n--- 3. Circular Reference Handling ---');
  const obj3: any = { name: 'Bob' };
  obj3.self = obj3;
  console.log(circularHandler.stringify(obj3));

  // 4. Filtered serialization
  console.log('\n--- 4. Filtered Serialization ---');
  const obj4 = {
    id: 1,
    name: 'Charlie',
    password: 'secret',
    email: '[email protected]'
  };
  console.log(filteredSerializer.serializeWithExclusion(obj4, ['password']));

  // 5. Compact serialization
  console.log('\n--- 5. Compact Serialization ---');
  const obj5 = { a: 1, b: null, c: undefined, d: 2 };
  console.log(compactSerializer.removeEmpty(obj5));

  console.log('\n=== All JSON Serialization Examples Completed ===');
}

// Export functions
export { JSONSerializer, CircularReferenceHandler, CustomTypeSerializer, CompactSerializer, StreamSerializer, ValidationSerializer, FilteredSerializer };
export { demonstrateJSONSerialization };

💻 JSON Deserialization typescript

🟡 intermediate ⭐⭐⭐

Parse JSON strings to JavaScript objects with revivers, type conversion, and error handling

⏱️ 25 min 🏷️ typescript, web, serialization, json
Prerequisites: Intermediate TypeScript, JSON API
// Web TypeScript JSON Deserialization Examples
// Parsing JSON strings to JavaScript objects with various options

// 1. Basic JSON Deserialization
class JSONDeserializer {
  // Parse JSON string
  parse<T = any>(json: string): T {
    return JSON.parse(json);
  }

  // Parse with custom reviver
  parseWithReviver<T = any>(json: string, reviver: (key: string, value: any) => any): T {
    return JSON.parse(json, reviver);
  }

  // Parse with date handling
  parseWithDates<T = any>(json: string): T {
    return JSON.parse(json, (key, value) => {
      if (typeof value === 'string') {
        // Try to parse as date
        const date = new Date(value);
        if (!isNaN(date.getTime())) {
          return date;
        }
      }
      return value;
    });
  }

  // Safe parse with error handling
  safeParse<T = any>(json: string, defaultValue: T): T {
    try {
      return JSON.parse(json);
    } catch (error) {
      console.error('JSON parse error:', error);
      return defaultValue;
    }
  }

  // Parse with validation
  parseWithValidation<T = any>(json: string, validator: (obj: any) => boolean): T | null {
    try {
      const obj = JSON.parse(json);
      if (validator(obj)) {
        return obj;
      }
      return null;
    } catch (error) {
      console.error('JSON parse error:', error);
      return null;
    }
  }
}

// 2. Custom Type Deserialization
class CustomTypeDeserializer {
  // Deserialize with type metadata
  deserializeWithType<T = any>(json: string): T {
    return JSON.parse(json, (key, value) => {
      if (value && typeof value === 'object' && value.__type) {
        switch (value.__type) {
          case 'Date':
            return new Date(value.value);
          case 'Map':
            return new Map(value.value);
          case 'Set':
            return new Set(value.value);
          case 'RegExp':
            return new RegExp(value.value.source, value.value.flags);
          case 'Error':
            const error = new Error(value.value.message);
            error.name = value.value.name;
            error.stack = value.value.stack;
            return error;
          case 'BigInt':
            return BigInt(value.value);
          default:
            return value;
        }
      }
      return value;
    });
  }

  // Deserialize class instances
  deserializeClassInstance<T = any>(json: string): T {
    return JSON.parse(json, (key, value) => {
      if (value && typeof value === 'object' && value.__class) {
        // Would need class registry for actual instantiation
        const { __class, ...data } = value;
        return data;
      }
      return value;
    });
  }

  // Deserialize with custom converters
  deserializeWithConverters<T = any>(
    json: string,
    converters: Map<string, (value: any) => any>
  ): T {
    return JSON.parse(json, (key, value) => {
      if (value && typeof value === 'object' && value.__type) {
        const converter = converters.get(value.__type);
        if (converter) {
          return converter(value.value);
        }
      }
      return value;
    });
  }
}

// 3. Schema-Based Deserializer
class SchemaDeserializer {
  // Deserialize with schema validation
  deserializeWithSchema<T = any>(json: string, schema: any): T | null {
    try {
      const obj = JSON.parse(json);
      if (this.validate(obj, schema)) {
        return this.transform(obj, schema);
      }
      return null;
    } catch (error) {
      console.error('JSON parse error:', error);
      return null;
    }
  }

  // Deserialize with type coercion
  deserializeWithCoercion<T = any>(json: string, types: Record<string, string>): T {
    const obj = JSON.parse(json);

    for (const key in types) {
      if (key in obj) {
        obj[key] = this.coerceType(obj[key], types[key]);
      }
    }

    return obj;
  }

  private validate(obj: any, schema: any): boolean {
    if (schema.type === 'object') {
      if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
        return false;
      }
      if (schema.required) {
        for (const field of schema.required) {
          if (!(field in obj)) {
            return false;
          }
        }
      }
    } else if (schema.type === 'array') {
      if (!Array.isArray(obj)) {
        return false;
      }
    }
    return true;
  }

  private transform(obj: any, schema: any): any {
    if (schema.type === 'object' && schema.properties) {
      for (const key in schema.properties) {
        if (key in obj) {
          obj[key] = this.coerceType(obj[key], schema.properties[key].type);
        }
      }
    }
    return obj;
  }

  private coerceType(value: any, type: string): any {
    switch (type) {
      case 'string':
        return String(value);
      case 'number':
        return Number(value);
      case 'boolean':
        return Boolean(value);
      case 'date':
        return new Date(value);
      default:
        return value;
    }
  }
}

// 4. Streaming Deserializer
class StreamingDeserializer {
  // Chunk-based parsing
  async* parseInChunks(json: string, chunkSize: number): AsyncGenerator<any> {
    for (let i = 0; i < json.length; i += chunkSize) {
      const chunk = json.slice(i, Math.min(i + chunkSize, json.length));
      yield this.parsePartial(chunk);
    }
  }

  // Parse JSON lines (NDJSON)
  parseJSONLines(lines: string): any[] {
    return lines.split('\n')
      .filter(line => line.trim())
      .map(line => JSON.parse(line));
  }

  // Parse JSON stream
  async parseStream(stream: ReadableStream): Promise<any> {
    const reader = stream.getReader();
    let json = '';

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      json += new TextDecoder().decode(value);
    }

    return JSON.parse(json);
  }

  private parsePartial(chunk: string): string {
    // Partial parsing - just return the chunk
    return chunk;
  }
}

// 5. Path-Based Deserializer
class PathBasedDeserializer {
  // Get value by path
  getByPath<T = any>(json: string, path: string): T | null {
    try {
      const obj = JSON.parse(json);
      const keys = path.split('.');
      let value: any = obj;

      for (const key of keys) {
        if (value && typeof value === 'object' && key in value) {
          value = value[key];
        } else {
          return null;
        }
      }

      return value;
    } catch (error) {
      console.error('JSON parse error:', error);
      return null;
    }
  }

  // Get multiple values by paths
  getMultiplePaths(json: string, paths: string[]): Record<string, any> {
    const obj = JSON.parse(json);
    const result: Record<string, any> = {};

    for (const path of paths) {
      result[path] = this.getByPathFromObject(obj, path);
    }

    return result;
  }

  // Extract nested objects
  extractNested(json: string, rootPath: string): any {
    try {
      const obj = JSON.parse(json);
      const root = this.getByPathFromObject(obj, rootPath);

      if (root && typeof root === 'object') {
        return root;
      }

      return null;
    } catch (error) {
      console.error('JSON parse error:', error);
      return null;
    }
  }

  private getByPathFromObject(obj: any, path: string): any {
    const keys = path.split('.');
    let value: any = obj;

    for (const key of keys) {
      if (value && typeof value === 'object' && key in value) {
        value = value[key];
      } else {
        return null;
      }
    }

    return value;
  }
}

// 6. Secure Deserializer
class SecureDeserializer {
  // Sanitize and parse
  sanitizeParse<T = any>(json: string, maxDepth: number = 100): T | null {
    try {
      let depth = 0;

      const sanitized = JSON.parse(json, (key, value) => {
        depth++;

        if (depth > maxDepth) {
          throw new Error('Maximum depth exceeded');
        }

        // Remove potentially dangerous values
        if (typeof value === 'function') {
          return undefined;
        }

        return value;
      });

      return sanitized;
    } catch (error) {
      console.error('JSON parse error:', error);
      return null;
    }
  }

  // Parse with size limit
  parseWithLimit<T = any>(json: string, maxSize: number): T | null {
    if (json.length > maxSize) {
      console.error('JSON size exceeds limit');
      return null;
    }

    try {
      return JSON.parse(json);
    } catch (error) {
      console.error('JSON parse error:', error);
      return null;
    }
  }

  // Parse with prototype protection
  parseWithProtection<T = any>(json: string): T | null {
    try {
      return JSON.parse(json, (key, value) => {
        // Prevent prototype pollution
        if (key === '__proto__' || key === 'constructor') {
          return undefined;
        }
        return value;
      });
    } catch (error) {
      console.error('JSON parse error:', error);
      return null;
    }
  }
}

// 7. Transformer Deserializer
class TransformerDeserializer {
  // Parse and transform
  parseAndTransform<T = any, R = any>(
    json: string,
    transformer: (obj: T) => R
  ): R | null {
    try {
      const obj = JSON.parse(json);
      return transformer(obj);
    } catch (error) {
      console.error('JSON parse or transform error:', error);
      return null;
    }
  }

  // Parse with field mapping
  parseWithMapping<T = any>(json: string, fieldMapping: Record<string, string>): T {
    const obj = JSON.parse(json);
    const result: any = {};

    for (const oldKey in fieldMapping) {
      const newKey = fieldMapping[oldKey];
      if (oldKey in obj) {
        result[newKey] = obj[oldKey];
      }
    }

    // Copy unmapped fields
    for (const key in obj) {
      if (!(key in fieldMapping)) {
        result[key] = obj[key];
      }
    }

    return result;
  }

  // Parse with computed fields
  parseWithComputed<T = any>(
    json: string,
    computed: Record<string, (obj: any) => any>
  ): T {
    const obj = JSON.parse(json);

    for (const key in computed) {
      obj[key] = computed[key](obj);
    }

    return obj;
  }
}

// Usage Examples
async function demonstrateJSONDeserialization() {
  console.log('=== Web TypeScript JSON Deserialization Examples ===\n');

  const deserializer = new JSONDeserializer();
  const customDeserializer = new CustomTypeDeserializer();
  const pathDeserializer = new PathBasedDeserializer();
  const secureDeserializer = new SecureDeserializer();

  // 1. Basic parsing
  console.log('--- 1. Basic Parsing ---');
  const json1 = '{ "name": "Alice", "age": 30 }';
  const obj1 = deserializer.parse(json1);
  console.log(obj1);

  // 2. Safe parsing
  console.log('\n--- 2. Safe Parsing ---');
  const invalidJson = '{ invalid }';
  const safeObj = deserializer.safeParse(invalidJson, { name: 'Default' });
  console.log(safeObj);

  // 3. Date parsing
  console.log('\n--- 3. Date Parsing ---');
  const json2 = '{ "date": "2024-01-15T10:30:00Z" }';
  const obj2 = deserializer.parseWithDates(json2);
  console.log(obj2);

  // 4. Custom type deserialization
  console.log('\n--- 4. Custom Type Deserialization ---');
  const json3 = '{ "map": { "__type": "Map", "value": [["key", "value"]] } }';
  const obj3 = customDeserializer.deserializeWithType(json3);
  console.log(obj3);

  // 5. Path-based access
  console.log('\n--- 5. Path-Based Access ---');
  const json4 = '{ "user": { "name": "Bob", "address": { "city": "NYC" } } }';
  const city = pathDeserializer.getByPath(json4, 'user.address.city');
  console.log(`City: ${city}`);

  // 6. Secure parsing
  console.log('\n--- 6. Secure Parsing ---');
  const json5 = '{ "__proto__": { "polluted": true }, "data": "test" }';
  const secureObj = secureDeserializer.parseWithProtection(json5);
  console.log(secureObj);

  console.log('\n=== All JSON Deserialization Examples Completed ===');
}

// Export functions
export { JSONDeserializer, CustomTypeDeserializer, SchemaDeserializer, StreamingDeserializer, PathBasedDeserializer, SecureDeserializer, TransformerDeserializer };
export { demonstrateJSONDeserialization };

💻 XML Parsing typescript

🟡 intermediate ⭐⭐⭐⭐

Parse and manipulate XML documents using DOM parser and XPath

⏱️ 30 min 🏷️ typescript, web, serialization, xml
Prerequisites: Intermediate TypeScript, XML, DOM API
// Web TypeScript XML Parsing Examples
// Using DOMParser, XMLSerializer, and XPath for XML processing

// 1. Basic XML Parser
class XMLParser {
  private parser: DOMParser;
  private serializer: XMLSerializer;

  constructor() {
    this.parser = new DOMParser();
    this.serializer = new XMLSerializer();
  }

  // Parse XML string to DOM
  parse(xmlString: string): XMLDocument {
    const doc = this.parser.parseFromString(xmlString, 'text/xml');

    // Check for parsing errors
    const parserError = doc.querySelector('parsererror');
    if (parserError) {
      throw new Error(`XML parsing error: ${parserError.textContent}`);
    }

    return doc;
  }

  // Serialize DOM to XML string
  serialize(doc: XMLDocument): string {
    return this.serializer.serializeToString(doc);
  }

  // Parse with error handling
  safeParse(xmlString: string): XMLDocument | null {
    try {
      return this.parse(xmlString);
    } catch (error) {
      console.error('XML parse error:', error);
      return null;
    }
  }
}

// 2. XML Navigator
class XMLNavigator {
  private doc: XMLDocument;

  constructor(doc: XMLDocument) {
    this.doc = doc;
  }

  // Get root element
  getRoot(): Element {
    return this.doc.documentElement;
  }

  // Get element by tag name
  getElementsByTagName(tagName: string): Element[] {
    const elements = this.doc.getElementsByTagName(tagName);
    return Array.from(elements);
  }

  // Get element by ID
  getElementById(id: string): Element | null {
    return this.doc.getElementById(id);
  }

  // Get child elements
  getChildren(element: Element): Element[] {
    return Array.from(element.children);
  }

  // Get parent element
  getParent(element: Element): Element | null {
    return element.parentElement;
  }

  // Get siblings
  getSiblings(element: Element): Element[] {
    const parent = element.parentElement;
    if (!parent) return [];

    return Array.from(parent.children).filter(child => child !== element);
  }

  // Get next sibling
  getNextSibling(element: Element): Element | null {
    return element.nextElementSibling;
  }

  // Get previous sibling
  getPreviousSibling(element: Element): Element | null {
    return element.previousElementSibling;
  }
}

// 3. XML Query Engine
class XMLQueryEngine {
  private doc: XMLDocument;

  constructor(doc: XMLDocument) {
    this.doc = doc;
  }

  // Query with XPath
  queryXPath(xpath: string): Node[] {
    const result = this.doc.evaluate(
      xpath,
      this.doc,
      null,
      XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
      null
    );

    const nodes: Node[] = [];
    for (let i = 0; i < result.snapshotLength; i++) {
      nodes.push(result.snapshotItem(i)!);
    }

    return nodes;
  }

  // Query with XPath and get first result
  queryXPathFirst(xpath: string): Node | null {
    const results = this.queryXPath(xpath);
    return results.length > 0 ? results[0] : null;
  }

  // Query with CSS selector (limited support)
  querySelector(selector: string): Element | null {
    return this.doc.querySelector(selector);
  }

  // Query all with CSS selector
  querySelectorAll(selector: string): Element[] {
    const elements = this.doc.querySelectorAll(selector);
    return Array.from(elements);
  }

  // Query elements by attribute
  findByAttribute(name: string, value: string): Element[] {
    const xpath = `//*[@${name}='${value}']`;
    return this.queryXPath(xpath) as Element[];
  }

  // Query elements containing text
  findByText(text: string): Element[] {
    const xpath = `//*[contains(text(), '${text}')]`;
    return this.queryXPath(xpath) as Element[];
  }
}

// 4. XML Attribute Handler
class XMLAttributeHandler {
  private element: Element;

  constructor(element: Element) {
    this.element = element;
  }

  // Get attribute value
  get(name: string): string | null {
    return this.element.getAttribute(name);
  }

  // Set attribute value
  set(name: string, value: string): void {
    this.element.setAttribute(name, value);
  }

  // Remove attribute
  remove(name: string): void {
    this.element.removeAttribute(name);
  }

  // Check if attribute exists
  has(name: string): boolean {
    return this.element.hasAttribute(name);
  }

  // Get all attributes
  getAll(): Record<string, string> {
    const attrs: Record<string, string> = {};

    for (let i = 0; i < this.element.attributes.length; i++) {
      const attr = this.element.attributes[i];
      attrs[attr.name] = attr.value;
    }

    return attrs;
  }

  // Get attribute names
  getNames(): string[] {
    return Array.from(this.element.attributes).map(attr => attr.name);
  }
}

// 5. XML Content Extractor
class XMLContentExtractor {
  private doc: XMLDocument;

  constructor(doc: XMLDocument) {
    this.doc = doc;
  }

  // Get text content of element
  getText(element: Element): string {
    return element.textContent || '';
  }

  // Get text content of multiple elements
  getTexts(xpath: string): string[] {
    const elements = this.queryElements(xpath);
    return elements.map(el => this.getText(el));
  }

  // Get HTML content
  getHTML(element: Element): string {
    return element.innerHTML;
  }

  // Get CDATA sections
  getCDATASections(element: Element): string[] {
    const cdataSections: string[] = [];

    const children = element.childNodes;
    for (let i = 0; i < children.length; i++) {
      if (children[i].nodeType === Node.CDATA_SECTION_NODE) {
        cdataSections.push(children[i].textContent || '');
      }
    }

    return cdataSections;
  }

  // Get comments
  getComments(element: Element): string[] {
    const comments: string[] = [];

    const children = element.childNodes;
    for (let i = 0; i < children.length; i++) {
      if (children[i].nodeType === Node.COMMENT_NODE) {
        comments.push(children[i].textContent || '');
      }
    }

    return comments;
  }

  private queryElements(xpath: string): Element[] {
    const result = this.doc.evaluate(
      xpath,
      this.doc,
      null,
      XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
      null
    );

    const elements: Element[] = [];
    for (let i = 0; i < result.snapshotLength; i++) {
      const node = result.snapshotItem(i);
      if (node && node.nodeType === Node.ELEMENT_NODE) {
        elements.push(node as Element);
      }
    }

    return elements;
  }
}

// 6. XML to JSON Converter
class XMLToJSONConverter {
  private doc: XMLDocument;

  constructor(doc: XMLDocument) {
    this.doc = doc;
  }

  // Convert XML to JSON object
  toJSON(rootElement?: Element): any {
    const element = rootElement || this.doc.documentElement;
    return this.elementToJSON(element);
  }

  // Convert element to JSON
  private elementToJSON(element: Element): any {
    const obj: any = {};

    // Add attributes
    if (element.attributes.length > 0) {
      obj['@attributes'] = {};
      for (let i = 0; i < element.attributes.length; i++) {
        const attr = element.attributes[i];
        obj['@attributes'][attr.name] = attr.value;
      }
    }

    // Add child elements
    const children = Array.from(element.children);

    if (children.length === 0) {
      // No children, use text content
      obj['#text'] = element.textContent;
    } else {
      // Group child elements by tag name
      const grouped: Record<string, Element[]> = {};

      for (const child of children) {
        const tagName = child.tagName;

        if (!grouped[tagName]) {
          grouped[tagName] = [];
        }

        grouped[tagName].push(child);
      }

      // Convert grouped children
      for (const tagName in grouped) {
        const elements = grouped[tagName];

        if (elements.length === 1) {
          obj[tagName] = this.elementToJSON(elements[0]);
        } else {
          obj[tagName] = elements.map(el => this.elementToJSON(el));
        }
      }
    }

    return obj;
  }

  // Convert to simplified JSON (no attributes or special keys)
  toSimpleJSON(): any {
    return this.elementToSimpleJSON(this.doc.documentElement);
  }

  private elementToSimpleJSON(element: Element): any {
    const children = Array.from(element.children);

    if (children.length === 0) {
      return element.textContent;
    }

    const obj: any = {};
    const grouped: Record<string, Element[]> = {};

    for (const child of children) {
      const tagName = child.tagName;

      if (!grouped[tagName]) {
        grouped[tagName] = [];
      }

      grouped[tagName].push(child);
    }

    for (const tagName in grouped) {
      const elements = grouped[tagName];

      if (elements.length === 1) {
        obj[tagName] = this.elementToSimpleJSON(elements[0]);
      } else {
        obj[tagName] = elements.map(el => this.elementToSimpleJSON(el));
      }
    }

    return obj;
  }
}

// 7. XML Modifier
class XMLModifier {
  private doc: XMLDocument;

  constructor(doc: XMLDocument) {
    this.doc = doc;
  }

  // Create new element
  createElement(tagName: string, attributes?: Record<string, string>): Element {
    const element = this.doc.createElement(tagName);

    if (attributes) {
      for (const name in attributes) {
        element.setAttribute(name, attributes[name]);
      }
    }

    return element;
  }

  // Append child element
  appendChild(parent: Element, child: Element): void {
    parent.appendChild(child);
  }

  // Remove element
  removeElement(element: Element): void {
    element.remove();
  }

  // Replace element
  replaceElement(oldElement: Element, newElement: Element): void {
    oldElement.parentNode?.replaceChild(newElement, oldElement);
  }

  // Clone element
  cloneElement(element: Element, deep: boolean = true): Element {
    return element.cloneNode(deep) as Element;
  }

  // Insert before
  insertBefore(reference: Element, newElement: Element): void {
    reference.parentNode?.insertBefore(newElement, reference);
  }

  // Insert after
  insertAfter(reference: Element, newElement: Element): void {
    reference.parentNode?.insertBefore(newElement, reference.nextSibling);
  }
}

// Usage Examples
async function demonstrateXMLParsing() {
  console.log('=== Web TypeScript XML Parsing Examples ===\n');

  const xmlString = `
    <library>
      <book id="1">
        <title>JavaScript Guide</title>
        <author>John Doe</author>
        <price>29.99</price>
      </book>
      <book id="2">
        <title>TypeScript Basics</title>
        <author>Jane Smith</author>
        <price>39.99</price>
      </book>
    </library>
  `;

  // 1. Parse XML
  console.log('--- 1. Parse XML ---');
  const parser = new XMLParser();
  const doc = parser.parse(xmlString);
  console.log('XML parsed successfully');

  // 2. Navigate XML
  console.log('\n--- 2. Navigate XML ---');
  const navigator = new XMLNavigator(doc);
  const root = navigator.getRoot();
  console.log(`Root element: ${root.tagName}`);

  const books = navigator.getElementsByTagName('book');
  console.log(`Found ${books.length} books`);

  // 3. Query XML
  console.log('\n--- 3. Query XML ---');
  const queryEngine = new XMLQueryEngine(doc);
  const titles = queryEngine.queryXPath('//library/book/title');
  titles.forEach(node => {
    console.log(`  Title: ${node.textContent}`);
  });

  // 4. Attributes
  console.log('\n--- 4. Attributes ---');
  const firstBook = books[0];
  const attrHandler = new XMLAttributeHandler(firstBook);
  console.log(`Book ID: ${attrHandler.get('id')}`);
  console.log('All attributes:', attrHandler.getAll());

  // 5. Content extraction
  console.log('\n--- 5. Content Extraction ---');
  const extractor = new XMLContentExtractor(doc);
  const bookTitles = extractor.getTexts('//library/book/title');
  console.log('Titles:', bookTitles);

  // 6. Convert to JSON
  console.log('\n--- 6. Convert to JSON ---');
  const converter = new XMLToJSONConverter(doc);
  const json = converter.toSimpleJSON();
  console.log(JSON.stringify(json, null, 2));

  // 7. Modify XML
  console.log('\n--- 7. Modify XML ---');
  const modifier = new XMLModifier(doc);
  const newBook = modifier.createElement('book', { id: '3' });

  const title = modifier.createElement('title');
  title.textContent = 'XML Master';
  newBook.appendChild(title);

  const author = modifier.createElement('author');
  author.textContent = 'Bob Wilson';
  newBook.appendChild(author);

  root.appendChild(newBook);

  const updatedXML = parser.serialize(doc);
  console.log(`Updated XML has ${navigator.getElementsByTagName('book').length} books`);

  console.log('\n=== All XML Parsing Examples Completed ===');
}

// Export functions
export { XMLParser, XMLNavigator, XMLQueryEngine, XMLAttributeHandler, XMLContentExtractor, XMLToJSONConverter, XMLModifier };
export { demonstrateXMLParsing };