UNPKG

@winglet/json

Version:

TypeScript library for safe and efficient JSON data manipulation with RFC 6901 (JSON Pointer) and RFC 6902 (JSON Patch) compliance, featuring prototype pollution protection and immutable operations

401 lines (284 loc) 10.6 kB
# @winglet/json [![TypeScript](https://img.shields.io/badge/typescript-✔-blue.svg)]() [![JSON Schema](https://img.shields.io/badge/JsonSchema-{}-blue.svg)]() [![RFC 6901](https://img.shields.io/badge/RFC%206901-compliant-green.svg)]() --- ## Overview `@winglet/json` is a TypeScript library for safe and efficient JSON data manipulation. It provides structured JSON data processing by strictly adhering to RFC 6901 (JSON Pointer) and RFC 6902 (JSON Patch) standards. ### Key Features - **Type Safety**: Full TypeScript support with compile-time type validation - **Standards Compliant**: Complete implementation of RFC 6901 (JSON Pointer) and RFC 6902 (JSON Patch) - **Security**: Built-in protection against prototype pollution attacks - **Flexibility**: Immutable and strict mode options for different use cases - **Performance**: Optimized algorithms supporting large-scale JSON data processing --- ## Installation ```bash # Using npm npm install @winglet/json # Using yarn yarn add @winglet/json # Using pnpm pnpm add @winglet/json ``` --- ## Sub-path Imports This package supports sub-path imports to enable more granular imports and optimize bundle size. You can import specific modules directly without importing the entire package: ```typescript // Main exports (all JSONPointer and JSONPath utilities) import { getValue, setValue } from '@winglet/json'; // JSONPath utilities import { JSONPath } from '@winglet/json/path'; // JSONPointer utilities import { getValue, setValue, escapePath, unescapePath, compare, applyPatch, difference, mergePatch } from '@winglet/json/pointer'; ``` ### Available Sub-paths Based on the package.json exports configuration: - `@winglet/json` - Main exports (all JSONPointer and JSONPath utilities) - `@winglet/json/path` - JSONPath constants and utilities - `@winglet/json/path-common` - JSONPath common utilities - `@winglet/json/pointer` - JSONPointer core utilities - `@winglet/json/pointer-common` - JSONPointer common utilities - `@winglet/json/pointer-escape` - JSONPointer escaping utilities (escapePath, unescapePath) - `@winglet/json/pointer-manipulator` - JSONPointer manipulation functions (getValue, setValue) - `@winglet/json/pointer-patch` - JSONPointer patch operations (compare, applyPatch, difference, mergePatch) --- ## Compatibility This package is written using ECMAScript 2020 (ES2020) syntax. **Supported Environments:** - Node.js 14.0.0 or higher - Modern browsers (with ES2020 support) **For Legacy Environment Support:** Use transpilers like Babel to convert the code to match your target environment. --- ## API Reference ### JSONPath Provides special character constants used in JSONPath expressions. #### Supported Operators - **[`JSONPath.Root`](./src/JSONPath/enum.ts)** (`$`): Root node of the JSON document - **[`JSONPath.Parent`](./src/JSONPath/enum.ts)** (`_`): Parent node of the current node - **[`JSONPath.Current`](./src/JSONPath/enum.ts)** (`@`): Currently processing node - **[`JSONPath.Child`](./src/JSONPath/enum.ts)** (`.`): Child node access operator - **[`JSONPath.Filter`](./src/JSONPath/enum.ts)** (`#`): Filter condition operator ### JSONPointer Provides a complete JSON Pointer implementation that fully complies with RFC 6901. #### Core Features ##### Data Manipulation **[`getValue`](./src/JSONPointer/utils/manipulator/getValue.ts)** ```typescript import { getValue } from '@winglet/json'; const data = { user: { profile: { name: 'Vincent', age: 30, }, }, }; const name = getValue(data, '/user/profile/name'); // Result: "Vincent" ``` **[`setValue`](./src/JSONPointer/utils/manipulator/setValue.ts)** ```typescript import { setValue } from '@winglet/json'; const data = { user: { profile: {} } }; const result = setValue(data, '/user/profile/email', 'vincent@example.com'); // Result: { user: { profile: { email: "vincent@example.com" } } } ``` ##### Escape Handling **[`escapePath`](./src/JSONPointer/utils/escape/escapePath.ts)** ```typescript import { escapePath } from '@winglet/json'; const escaped = escapePath('path/with~special'); // Result: "path~1with~0special" ``` **[`unescapePath`](./src/JSONPointer/utils/escape/unescapePath.ts)** ```typescript import { unescapePath } from '@winglet/json'; const unescaped = unescapePath('path~1with~0special'); // Result: "path/with~special" ``` ##### JSON Patch Operations **[`compare`](./src/JSONPointer/utils/patch/compare/compare.ts)** ```typescript import { compare } from '@winglet/json'; const source = { name: 'John', age: 30, city: 'NYC' }; const target = { name: 'John', age: 31, country: 'USA' }; const patches = compare(source, target); // Result: // [ // { op: "replace", path: "/age", value: 31 }, // { op: "remove", path: "/city" }, // { op: "add", path: "/country", value: "USA" } // ] ``` **[`applyPatch`](./src/JSONPointer/utils/patch/applyPatch/applyPatch.ts)** ```typescript import { applyPatch } from '@winglet/json'; const source = { name: 'John', age: 30 }; const patches = [ { op: 'replace', path: '/age', value: 31 }, { op: 'add', path: '/city', value: 'NYC' }, ]; const result = applyPatch(source, patches); // Result: { name: "John", age: 31, city: "NYC" } ``` **[`difference`](./src/JSONPointer/utils/patch/difference/difference.ts)** ```typescript import { difference } from '@winglet/json'; const source = { name: 'John', age: 30, city: 'NYC' }; const target = { name: 'John', age: 31, country: 'USA' }; const mergePatch = difference(source, target); // Result: { age: 31, city: null, country: "USA" } ``` **[`mergePatch`](./src/JSONPointer/utils/patch/mergePatch/mergePatch.ts)** ```typescript import { mergePatch } from '@winglet/json'; const source = { name: 'John', age: 30, temp: 'data' }; const patch = { age: 31, temp: null, city: 'NYC' }; const result = mergePatch(source, patch); // Result: { name: "John", age: 31, city: "NYC" } ``` #### Configuration Options ##### CompareOptions ```typescript interface CompareOptions { strict?: boolean; // Strict comparison mode (default: false) immutable?: boolean; // Immutable mode (default: true) } ``` ##### ApplyPatchOptions ```typescript interface ApplyPatchOptions { strict?: boolean; // Strict application mode (default: false) immutable?: boolean; // Immutable mode (default: true) protectPrototype?: boolean; // Prototype protection (default: true) } ``` --- ## Usage Examples ### Basic Usage ```typescript import { applyPatch, compare, getValue, setValue } from '@winglet/json'; // Complex JSON data const data = { users: [ { id: 1, name: 'Alice', preferences: { theme: 'dark' } }, { id: 2, name: 'Bob', preferences: { theme: 'light' } }, ], settings: { app: { version: '1.0.0' }, }, }; // Value retrieval const theme = getValue(data, '/users/0/preferences/theme'); console.log(theme); // "dark" // Value setting const updated = setValue(data, '/settings/app/version', '1.1.0'); // Change comparison const patches = compare(data, updated); console.log(patches); // [{ op: "replace", path: "/settings/app/version", value: "1.1.0" }] ``` ### Advanced Usage - Immutability and Security ```typescript import { applyPatch } from '@winglet/json'; const data = { user: { role: 'user' } }; const patches = [ { op: 'add', path: '/user/permissions', value: ['read', 'write'] }, { op: 'replace', path: '/user/role', value: 'admin' }, ]; // Safe patch application (prevents prototype pollution) const result = applyPatch(data, patches, { immutable: true, // Preserve original data protectPrototype: true, // Prevent prototype pollution strict: true, // Strict validation }); console.log(data); // Original data preserved console.log(result); // New modified object ``` ### JSON Merge Patch Usage ```typescript import { difference, mergePatch } from '@winglet/json'; const source = { user: { name: 'Alice', age: 25, role: 'admin', temp: 'data' }, settings: { theme: 'dark' }, }; const target = { user: { name: 'Bob', age: 25, permissions: ['read', 'write'] }, settings: { theme: 'light', language: 'en' }, }; // Generate JSON Merge Patch representing differences between two objects const patch = difference(source, target); console.log(patch); // { // user: { name: "Bob", role: null, temp: null, permissions: ["read", "write"] }, // settings: { theme: "light", language: "en" } // } // Apply JSON Merge Patch const result = mergePatch(source, patch); console.log(result); // { // user: { name: "Bob", age: 25, permissions: ["read", "write"] }, // settings: { theme: "light", language: "en" } // } ``` ### Array Manipulation ```typescript import { getValue, setValue } from '@winglet/json'; const data = { items: ['apple', 'banana', 'cherry'], }; // Array element access const secondItem = getValue(data, '/items/1'); // Result: "banana" // Add element to end of array (using RFC 6901 "-" syntax) const withNewItem = setValue(data, '/items/-', 'date'); // Result: { items: ["apple", "banana", "cherry", "date"] } ``` --- ## Error Handling ```typescript import { JSONPointerError, getValue } from '@winglet/json'; try { const value = getValue({}, '/nonexistent/path'); } catch (error) { if (error instanceof JSONPointerError) { console.error('JSON Pointer Error:', error.message); console.error('Error Code:', error.code); console.error('Additional Details:', error.details); } } ``` --- ## Performance Considerations - **Large Data**: Consider using `immutable: false` option when dealing with deeply nested objects or large arrays - **Frequent Changes**: Use `strict: false` to improve performance when applying many patches sequentially - **Memory Usage**: Immutable mode uses more memory but ensures safety --- ## Contributing If you'd like to contribute to this project: 1. Create an issue for bug reports or feature suggestions 2. Submit pull requests with improvements 3. Include test cases with your submissions --- ## License This repository is provided under the MIT License. See the [`LICENSE`](./LICENSE) file for details. --- ## Related Standards - [RFC 6901 - JavaScript Object Notation (JSON) Pointer](https://datatracker.ietf.org/doc/html/rfc6901) - [RFC 6902 - JavaScript Object Notation (JSON) Patch](https://datatracker.ietf.org/doc/html/rfc6902) - [RFC 7396 - JSON Merge Patch](https://datatracker.ietf.org/doc/html/rfc7396) - [JSONPath - XPath for JSON](https://goessner.net/articles/JsonPath/) --- ## Contact For questions or suggestions about this project, please create a GitHub issue.