feat(middleware): add MQTT payload parser with dot-notation JSON path extraction
- src/modules/mqtt/payload-parser.ts: extractValue() supporting \$.field and \$.nested.deep paths - Returns raw string when no path provided, null for invalid JSON or missing paths - Stringifies numbers/booleans, returns JSON string for objects/arrays - tests/payload-parser.test.ts: 19 tests covering no-path passthrough, simple/nested fields, missing paths, invalid JSON, and edge cases (null, zero, arrays) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Extract a value from a JSON payload using a simple dot-notation JSON path.
|
||||
*
|
||||
* Supported path format: "$.field" or "$.field.nested.deep"
|
||||
* - Returns the raw payload string if no path is provided (undefined, null, or empty string).
|
||||
* - Returns null if the JSON is invalid or the path does not exist.
|
||||
* - Returns the extracted value as a string (numbers, booleans, null are stringified).
|
||||
* - Objects and arrays are returned as their JSON string representation.
|
||||
*/
|
||||
export function extractValue(
|
||||
payload: string,
|
||||
jsonPath: string | null | undefined
|
||||
): string | null {
|
||||
// No path — return raw payload as-is
|
||||
if (!jsonPath) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
// Parse JSON
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(payload);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove leading "$." and split on dots
|
||||
const pathWithoutRoot = jsonPath.startsWith('$.') ? jsonPath.slice(2) : jsonPath;
|
||||
|
||||
if (!pathWithoutRoot) {
|
||||
// Path was just "$" — return the whole object as JSON
|
||||
return JSON.stringify(parsed);
|
||||
}
|
||||
|
||||
const segments = pathWithoutRoot.split('.');
|
||||
|
||||
// Traverse the parsed object
|
||||
let current: unknown = parsed;
|
||||
for (const segment of segments) {
|
||||
if (current === null || typeof current !== 'object' || Array.isArray(current)) {
|
||||
return null;
|
||||
}
|
||||
const obj = current as Record<string, unknown>;
|
||||
if (!Object.prototype.hasOwnProperty.call(obj, segment)) {
|
||||
return null;
|
||||
}
|
||||
current = obj[segment];
|
||||
}
|
||||
|
||||
// Convert result to string
|
||||
if (current === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (current === null) {
|
||||
return 'null';
|
||||
}
|
||||
if (typeof current === 'object') {
|
||||
// Arrays and objects: return JSON string without spaces
|
||||
return JSON.stringify(current);
|
||||
}
|
||||
return String(current);
|
||||
}
|
||||
Reference in New Issue
Block a user