/
h0tnanny
/
IotPlatform
Обзор
Документация
Войти
/
h0tnanny
/
IotPlatform
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
src/utils/rawPayloadParser.ts
126 строк
4 KB
h0tnanny
Парсинг данных как строчки
20 фев 2026, 19:31
20 фев 2026, 19:31
fbb7684
Код
Авторство
О чём код?
/** * Интерпретация произвольной строки от оборудования в JSON для системы. * Устройства не всегда шлют валидный JSON — поддерживаем key=value, CSV и т.д. */ export type PayloadFormat = 'json' | 'key_value' | 'csv'; export interface KeyValueOptions { /** Разделитель между парами (по умолчанию & и \n) */ pairSeparator?: string | string[]; /** Разделитель между ключом и значением (по умолчанию =) */ keyValueSeparator?: string; } export interface CsvOptions { /** Разделитель полей (по умолчанию ,) */ delimiter?: string; /** Имена полей по порядку (если не заданы — первая строка как заголовки или field_0, field_1...) */ fieldNames?: string[]; } export type RawPayloadOptions = KeyValueOptions | CsvOptions; const DEFAULT_PAIR_SEPARATORS = ['&', '\n', ';']; const DEFAULT_KEY_VALUE_SEP = '='; /** * Парсит сырую строку в объект по выбранному формату. * @param raw — строка от устройства (MQTT, HTTP body, UDP, CoAP) * @param format — формат: json, key_value (temperature=25&humidity=60), csv * @param options — опции для key_value или csv * @returns Объект ключ→значение; числа и булевы приводятся автоматически для key_value/csv */ export function interpretRawPayload( raw: string, format: PayloadFormat, options?: RawPayloadOptions ): Record<string, unknown> { const trimmed = raw.trim(); if (!trimmed) return {}; switch (format) { case 'json': return parseJson(trimmed); case 'key_value': return parseKeyValue(trimmed, (options as KeyValueOptions) ?? {}); case 'csv': return parseCsv(trimmed, (options as CsvOptions) ?? {}); default: return parseJson(trimmed); } } function parseJson(raw: string): Record<string, unknown> { const parsed = JSON.parse(raw); if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) { return parsed as Record<string, unknown>; } return { value: parsed }; } function parseKeyValue(raw: string, opts: KeyValueOptions): Record<string, unknown> { const pairSeps = opts.pairSeparator != null ? (Array.isArray(opts.pairSeparator) ? opts.pairSeparator : [opts.pairSeparator]) : DEFAULT_PAIR_SEPARATORS; const kvSep = opts.keyValueSeparator ?? DEFAULT_KEY_VALUE_SEP; let parts = [raw]; for (const sep of pairSeps) { parts = parts.flatMap((p) => p.split(sep)); } const result: Record<string, unknown> = {}; for (const part of parts) { const eqIndex = part.indexOf(kvSep); if (eqIndex === -1) continue; const key = part.slice(0, eqIndex).trim(); let valueStr = part.slice(eqIndex + kvSep.length).trim(); try { valueStr = decodeURIComponent(valueStr); } catch { // оставляем как есть, если не URL-encoded } if (!key) continue; result[key] = coerceValue(valueStr); } return result; } function parseCsv(raw: string, opts: CsvOptions): Record<string, unknown> { const delimiter = opts.delimiter ?? ','; const fieldNames = opts.fieldNames; const lines = raw.split(/\r?\n/).filter((l) => l.trim()); if (lines.length === 0) return {}; const values = lines[0].split(delimiter).map((s) => s.trim()); const names: string[] = fieldNames ?? values.map((_, i) => `field_${i}`); if (fieldNames && fieldNames.length !== values.length) { return Object.fromEntries( values.map((v, i) => [names[i] ?? `field_${i}`, coerceValue(v)]) ); } if (!fieldNames && lines.length > 1) { const firstAsHeaders = values; const secondLine = lines[1].split(delimiter).map((s) => s.trim()); return Object.fromEntries( secondLine.map((v, i) => [firstAsHeaders[i] ?? `field_${i}`, coerceValue(v)]) ); } return Object.fromEntries( values.map((v, i) => [names[i], coerceValue(v)]) ); } function coerceValue(s: string): unknown { const lower = s.toLowerCase(); if (lower === 'true') return true; if (lower === 'false') return false; if (lower === 'null' || s === '') return null; const num = Number(s); if (s.trim() !== '' && !Number.isNaN(num)) return num; return s; }