Converter - 핵심 개념
이 가이드는 StAX-XML Converter를 효과적으로 사용하기 위해 이해해야 할 기본 개념을 다룹니다.
스키마 빌더 (x)
섹션 제목: “스키마 빌더 (x)”x 객체는 XML 스키마를 생성하는 주요 인터페이스입니다:
import { x } from 'stax-xml/converter';
// 스키마 생성const stringSchema = x.string();const numberSchema = x.number();const objectSchema = x.object({...});const arraySchema = x.array(elementSchema, xpath);Fluent API 패턴
섹션 제목: “Fluent API 패턴”모든 스키마 메서드는 새 스키마 인스턴스를 반환하여 API를 불변하고 체이닝 가능하게 만듭니다:
const schema = x.number() .xpath('//price') .min(0) .max(1000);
// 각 메서드는 새 스키마를 반환const baseNumber = x.number();const withXPath = baseNumber.xpath('//value'); // 새 인스턴스const withMin = withXPath.min(0); // 새 인스턴스
// 원본은 변경되지 않음console.log(baseNumber.options.xpath); // undefinedconsole.log(withXPath.options.xpath); // '//value'파싱 모드
섹션 제목: “파싱 모드”Converter는 다양한 사용 사례를 위한 여러 파싱 메서드를 제공합니다:
동기 파싱
섹션 제목: “동기 파싱”동기, 블로킹 파싱을 위해 parseSync() 사용:
const schema = x.string().xpath('//title');const result = schema.parseSync(xmlString);// 반환: string사용 시기:
- XML이 이미 문자열로 메모리에 있을 때
- 동기 컨텍스트에 있을 때
- 동기 blocking parsing이 필요할 때
비동기 파싱
섹션 제목: “비동기 파싱”비동기 파싱을 위해 parse() 사용:
const schema = x.object({ name: x.string().xpath('//name'), value: x.number().xpath('//value')});
const result = await schema.parse(xmlString);// 반환: Promise<{ name: string; value: number; }>안전한 파싱
섹션 제목: “안전한 파싱”안전한 파싱 메서드는 예외를 던지는 대신 결과 객체를 반환합니다:
const schema = x.number().xpath('//age').min(0).max(120);
// 안전한 동기 파싱const result = schema.safeParseSync('<age>150</age>');
if (result.success) { console.log(result.data); // number} else { console.log(result.error.issues); // 에러 객체 배열}타입 추론
섹션 제목: “타입 추론”Converter는 Infer 유틸리티 타입을 사용하여 자동 TypeScript 타입 추론을 제공합니다:
import { x, type Infer } from 'stax-xml/converter';
const userSchema = x.object({ id: x.number().xpath('//id'), username: x.string().xpath('//username'), email: x.string().xpath('//email'), active: x.string().xpath('//active').transform(v => v === 'true')});
// 자동으로 타입 추론type User = Infer<typeof userSchema>;// {// id: number;// username: string;// email: string;// active: boolean; // 주의: 변환된 타입!// }
// 추론된 타입 사용const users: User[] = [];복잡한 타입 추론
섹션 제목: “복잡한 타입 추론”타입 추론은 중첩 구조 및 변환과 함께 작동합니다:
const productSchema = x.object({ name: x.string().xpath('//name'), price: x.number().xpath('//price'), tags: x.array(x.string(), '//tag'), specs: x.object({ weight: x.number().xpath('./weight'), dimensions: x.string().xpath('./dimensions') }).xpath('//specs').optional()});
type Product = Infer<typeof productSchema>;// {// name: string;// price: number;// tags: string[];// specs: { weight: number; dimensions: string; } | undefined;// }XPath 통합
섹션 제목: “XPath 통합”XPath는 XML 문서에서 요소를 선택하는 주요 방법입니다.
XPath 기초
섹션 제목: “XPath 기초”// 루트에서 절대 경로x.string().xpath('/root/element/child')
// 하위 요소 검색 (문서 어디서나)x.string().xpath('//element')
// 속성 접근x.string().xpath('/root/@id')x.string().xpath('//link/@href')
// 결합x.string().xpath('/root/item/@name')다양한 스키마의 XPath
섹션 제목: “다양한 스키마의 XPath”String 및 Number 스키마:
const title = x.string().xpath('/book/title');const price = x.number().xpath('/book/price');Object 스키마:
// 개별 필드의 XPathconst book = x.object({ title: x.string().xpath('/book/title'), author: x.string().xpath('/book/author')});
// 전체 객체의 XPath (자식 XPath 범위 지정)const book = x.object({ title: x.string().xpath('./title'), // /book에 상대적 author: x.string().xpath('./author') // /book에 상대적}).xpath('/book');Array 스키마:
// 배열에는 XPath 필수const items = x.array( x.string(), '//item' // 모든 <item> 요소 선택);에러 처리
섹션 제목: “에러 처리”Converter는 파싱이 실패할 때 상세한 에러 정보를 제공합니다:
XmlParseError
섹션 제목: “XmlParseError”import { XmlParseError } from 'stax-xml/converter';
try { const result = schema.parseSync(invalidXml);} catch (error) { if (error instanceof XmlParseError) { console.log(error.message); // 사람이 읽을 수 있는 메시지 console.log(error.issues); // 구체적인 문제 배열 }}에러 복구 전략
섹션 제목: “에러 복구 전략”1. Optional 사용
const schema = x.object({ id: x.number().xpath('//id'), optional: x.string().xpath('//optional').optional()});// 반환: { id: number; optional: string | undefined }2. 안전한 파싱 사용
const result = schema.safeParseSync(xml);if (!result.success) { // 에러를 우아하게 처리 return defaultValue;}return result.data;3. 기본값을 위한 Transform 사용
const schema = x.string() .xpath('//value') .transform(v => v || 'default');스키마 구성
섹션 제목: “스키마 구성”스키마는 구성하고 재사용할 수 있습니다:
스키마 재사용
섹션 제목: “스키마 재사용”// 재사용 가능한 스키마 정의const priceSchema = x.number().min(0);const idSchema = x.number().int().min(1);
// 객체에서 구성const productSchema = x.object({ id: idSchema.xpath('//id'), price: priceSchema.xpath('//price'), salePrice: priceSchema.xpath('//salePrice').optional()});중첩 객체
섹션 제목: “중첩 객체”const addressSchema = x.object({ street: x.string().xpath('./street'), city: x.string().xpath('./city'), zip: x.string().xpath('./zip')}).xpath('/address');
const personSchema = x.object({ name: x.string().xpath('/person/name'), address: addressSchema // 중첩된 객체 스키마});성능 고려사항
섹션 제목: “성능 고려사항”동기 vs 비동기
섹션 제목: “동기 vs 비동기”// 동기 - 작은 문서에 약간 더 빠름const result = schema.parseSync(smallXml);
// 비동기 - 큰 문서에 더 좋음const result = await schema.parse(largeXml);XPath 최적화
섹션 제목: “XPath 최적화”// ❌ 느림 - 전체 문서를 여러 번 검색const schema = x.object({ a: x.string().xpath('//a'), b: x.string().xpath('//b'), c: x.string().xpath('//c')});
// ✅ 더 좋음 - 단일 루트 XPath, 상대 자식const schema = x.object({ a: x.string().xpath('./a'), b: x.string().xpath('./b'), c: x.string().xpath('./c')}).xpath('/root');자동 IR dispatch와 선택적 warm-up
섹션 제목: “자동 IR dispatch와 선택적 warm-up”Schema는 처음 사용할 때 IR dispatch program을 자동으로 만들고 cache합니다. Public
compile() 단계는 없습니다. 일회성 비용을 server 또는 worker startup으로 옮길 때만
precompile()을 사용하세요.
const personSchema = x.object({ id: x.number().xpath('./@id').int(), name: x.string().xpath('./name'), age: x.number().xpath('./age').int(), birthday: x.string().xpath('./birthday'), married: x.string() .xpath('./married') .transform(value => value === 'true'), firstTime: x.string().xpath('./married/@firstTime'), nickname: x.string().xpath('./nickname').optional(), address: x.object({ city: x.string().xpath('./city'), zip: x.string().xpath('./zip/text()') }).xpath('./address'), aliases: x.array(x.string(), './alias'), contacts: x.array( x.object({ type: x.string().xpath('./@type'), value: x.string().xpath('./value/text()') }), './contact' )});
const schema = x.object({ datasetId: x.string().xpath('/dataset/@id'), title: x.string().xpath('/dataset/title/text()'), metadata: x.object({ source: x.string().xpath('./source'), generatedAt: x.string().xpath('./generatedAt') }).xpath('/dataset/metadata'), labels: x.array(x.string(), '/dataset/labels/label'), people: x.array(personSchema, '//person')}).precompile();
// 요청 시점에는 warm-up된 program을 재사용합니다.const result = schema.parseSync(xml);precompile()은 선택 사항입니다. 호출하지 않아도 parse()와 parseSync()가 같은
작업을 자동 수행하고 cache된 program을 재사용합니다. Warm-up은 최초 요청 latency의
발생 시점만 바꾸며 steady-state throughput을 높이지 않습니다. 지원되는 public selector
중 고정 XML event dispatch로 lowering할 수 있는 형태는 fast path를 사용합니다.
위 스키마는 주요 fast-path shape를 하나의 compiled schema 안에 조합한 예시입니다. 절대 element/attribute selector, 직접 text() selector, //person descendant 배열 경계, person item 내부의 상대 selector, 중첩 객체, scalar 배열, 객체 배열, optional 필드, transform을 모두 포함합니다. .int() 같은 숫자 검증은 텍스트 추출 후 그대로 적용됩니다.
fast path에 적합한 형태:
| 형태 | 예시 |
|---|---|
| 절대 요소 경로 | /catalog/book/title |
| descendant 요소 경로 | //book |
| 절대 또는 상대 속성 | /catalog/book/@id, ./@id |
| 객체 또는 배열 item 안의 상대 필드 | ./title, ./author/name |
| 직접 텍스트 선택 | /message/text(), ./title/text() |
| scalar 필드를 가진 객체 | x.object({ title: x.string().xpath('./title') }).xpath('/book') |
| scalar 또는 객체 배열 | x.array(x.string(), '/tags/tag'), x.array(bookSchema, '/catalog/book') |
| 중첩 객체, optional 필드, transform | x.object({...}).optional().transform(...) |
지원하지 않는 streaming selector 또는 schema 형태:
| 형태 | 예시 |
|---|---|
| wildcard | /catalog/* |
| arbitrary predicate | //book[@id="1"], //book[last()] |
./가 없는 모호한 상대 경로 |
title |
| 중첩 배열 | x.array(x.array(x.string(), './value'), '/group') |
| 배열 XPath와 element XPath를 동시에 지정한 배열 | x.array(x.string().xpath('./title'), '/book') |
| custom 또는 미지원 스키마 wrapper | 사용자 정의 schema subclass |
지원하지 않는 XPath expression은 document-tree evaluator로 조용히 전환하지 않고 명시적으로 실패합니다. 전체 selector 범위는 XPath contract를 확인하세요.
스키마 재사용
섹션 제목: “스키마 재사용”// ❌ 매번 새 스키마 생성function parseUser(xml: string) { const schema = x.object({...}); return schema.parseSync(xml);}
// ✅ 스키마를 한 번 정의하고 재사용const userSchema = x.object({...});
function parseUser(xml: string) { return userSchema.parseSync(xml);}