@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
Markdown
# @winglet/json
[]()
[]()
[]()
## 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.