fix(Smartjson): Cross-platform buffer/base64 handling, safer folding with cycle detection, parsing fixes, docs and dependency updates
This commit is contained in:
395
readme.md
395
readme.md
@@ -1,125 +1,352 @@
|
||||
# @push.rocks/smartjson
|
||||
typed json handlers
|
||||
**🚀 Typed JSON handling for modern Node.js and TypeScript applications**
|
||||
|
||||
## Install
|
||||
A powerful library for working with JSON in TypeScript, providing type-safe serialization, advanced buffer handling, deep object comparison, and support for complex class instances. Perfect for applications that need reliable JSON manipulation with full TypeScript support.
|
||||
|
||||
To install `@push.rocks/smartjson`, you can use npm or yarn as follows:
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
```bash
|
||||
# Using npm
|
||||
npm install @push.rocks/smartjson --save
|
||||
# or using yarn
|
||||
|
||||
# Using yarn
|
||||
yarn add @push.rocks/smartjson
|
||||
|
||||
# Using pnpm (recommended)
|
||||
pnpm add @push.rocks/smartjson
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Features
|
||||
|
||||
`@push.rocks/smartjson` offers typed JSON handling, including features like folding and enfolding classes from JSON, JSON parsing with support for buffers, and comparison of JSON objects for equality. This guide will walk through various use cases and scenarios to effectively utilize `@push.rocks/smartjson` in your projects.
|
||||
✨ **Type-Safe JSON Operations** - Full TypeScript support with proper typing
|
||||
🎯 **Class Instance Serialization** - Fold and unfold class instances to/from JSON
|
||||
🔐 **Buffer & Binary Support** - Seamless handling of Buffers and Typed Arrays
|
||||
📊 **JSON Lines Support** - Parse and compare JSONL data streams
|
||||
🎨 **Pretty Printing** - Beautiful formatted JSON output
|
||||
⚡ **Stable Stringification** - Consistent key ordering for reliable comparisons
|
||||
🔍 **Deep Equality Checks** - Compare complex objects and JSON structures
|
||||
🌐 **Base64 Encoding** - Built-in base64 JSON encoding/decoding
|
||||
|
||||
### Basic Import
|
||||
|
||||
First, make sure to import the module:
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import * as smartjson from '@push.rocks/smartjson';
|
||||
|
||||
// Parse JSON with automatic buffer handling
|
||||
const parsed = smartjson.parse('{"name":"example","data":{"type":"Buffer","data":[1,2,3]}}');
|
||||
|
||||
// Stringify with stable key ordering
|
||||
const json = smartjson.stringify({ z: 1, a: 2, m: 3 });
|
||||
// Result: '{"a":2,"m":3,"z":1}'
|
||||
|
||||
// Pretty print for human readability
|
||||
const pretty = smartjson.stringifyPretty({ hello: 'world', count: 42 });
|
||||
// Result:
|
||||
// {
|
||||
// "hello": "world",
|
||||
// "count": 42
|
||||
// }
|
||||
```
|
||||
|
||||
### Parsing and Stringifying JSON
|
||||
## Core Functions
|
||||
|
||||
- **Parsing JSON Strings:**
|
||||
|
||||
`smartjson` enhances JSON parsing by supporting JavaScript's typed arrays, particularly with `Buffer` handling.
|
||||
|
||||
```typescript
|
||||
const jsonString = '{"type":"Buffer","data":[116,101,115,116]}';
|
||||
const parsedObject = smartjson.parse(jsonString);
|
||||
console.log(parsedObject); // Output will be based on the content of jsonString
|
||||
```
|
||||
|
||||
- **Stringifying Objects:**
|
||||
|
||||
`@push.rocks/smartjson` provides a `stringify` function that can convert JavaScript objects into JSON strings, with special handling for `Buffer` objects.
|
||||
|
||||
```typescript
|
||||
const myObject = {
|
||||
exampleBuffer: new Uint8Array([116, 101, 115, 116])
|
||||
};
|
||||
const jsonString = smartjson.stringify(myObject);
|
||||
console.log(jsonString); // Will include `exampleBuffer` encoded in a special format
|
||||
```
|
||||
|
||||
### Working with Base64 Encoded JSON
|
||||
|
||||
For cases where JSON strings are encoded in base64 format, `smartjson` offers methods to encode and decode these strings transparently.
|
||||
### JSON Parsing and Stringification
|
||||
|
||||
```typescript
|
||||
const objectToEncode = { hello: 'world' };
|
||||
const base64EncodedJson = smartjson.stringifyBase64(objectToEncode);
|
||||
console.log(base64EncodedJson); // Encoded JSON string
|
||||
// Standard parsing with automatic Buffer detection
|
||||
const obj = smartjson.parse('{"hello":"world"}');
|
||||
|
||||
const decodedObject = smartjson.parseBase64(base64EncodedJson);
|
||||
console.log(decodedObject); // Original object
|
||||
// Stable stringification (consistent key ordering)
|
||||
const jsonStr = smartjson.stringify({ name: 'test', id: 1 });
|
||||
|
||||
// Pretty printing for debugging
|
||||
const prettyJson = smartjson.stringifyPretty({
|
||||
nested: {
|
||||
data: 'value'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Folding and Enfolding Classes
|
||||
### Base64 JSON Encoding
|
||||
|
||||
`@push.rocks/smartjson` allows you to fold (serialize) and enfold (deserialize) class instances to and from JSON. This is particularly useful when working with typed objects and you need to maintain type integrity across serialization.
|
||||
Encode JSON data as base64 for safe transmission:
|
||||
|
||||
- **Defining a Foldable Class:**
|
||||
```typescript
|
||||
const myData = {
|
||||
message: 'Hello World',
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
Decorate properties that should be included in JSON with `@smartjson.foldDec()`.
|
||||
// Encode to base64
|
||||
const encoded = smartjson.stringifyBase64(myData);
|
||||
console.log(encoded); // Base64 string
|
||||
|
||||
```typescript
|
||||
import { Smartjson, foldDec } from '@push.rocks/smartjson';
|
||||
// Decode back to object
|
||||
const decoded = smartjson.parseBase64(encoded);
|
||||
console.log(decoded); // Original object
|
||||
```
|
||||
|
||||
class MyDataModel extends Smartjson {
|
||||
@foldDec() public someProperty: string = 'default';
|
||||
|
||||
constructor(public id: number, someProperty?: string) {
|
||||
super();
|
||||
if (someProperty) this.someProperty = someProperty;
|
||||
}
|
||||
### JSON Lines (JSONL) Support
|
||||
|
||||
Perfect for streaming data and log processing:
|
||||
|
||||
```typescript
|
||||
// Parse JSON Lines format
|
||||
const jsonLines = `{"event":"start","time":1234}
|
||||
{"event":"data","value":42}
|
||||
{"event":"end","time":5678}`;
|
||||
|
||||
const events = smartjson.parseJsonL(jsonLines);
|
||||
// Result: Array of parsed objects
|
||||
|
||||
// Compare JSON Lines data
|
||||
const jsonL1 = `{"id":1}\n{"id":2}`;
|
||||
const jsonL2 = `{"id":1}\n{"id":2}`;
|
||||
const isEqual = smartjson.deepEqualJsonLStrings(jsonL1, jsonL2); // true
|
||||
```
|
||||
|
||||
## Advanced Class Serialization
|
||||
|
||||
### Creating Serializable Classes
|
||||
|
||||
Transform class instances to JSON and back while preserving type safety:
|
||||
|
||||
```typescript
|
||||
import { Smartjson, foldDec } from '@push.rocks/smartjson';
|
||||
|
||||
class User extends Smartjson {
|
||||
@foldDec() public username: string;
|
||||
@foldDec() public email: string;
|
||||
@foldDec() public settings: UserSettings;
|
||||
|
||||
// Properties without @foldDec won't be serialized
|
||||
private internalId: string;
|
||||
|
||||
constructor(username: string, email: string) {
|
||||
super();
|
||||
this.username = username;
|
||||
this.email = email;
|
||||
this.settings = new UserSettings();
|
||||
this.internalId = Math.random().toString();
|
||||
}
|
||||
```
|
||||
}
|
||||
|
||||
- **Folding and Enfolding Instances:**
|
||||
class UserSettings extends Smartjson {
|
||||
@foldDec() public theme: 'light' | 'dark' = 'light';
|
||||
@foldDec() public notifications: boolean = true;
|
||||
}
|
||||
|
||||
```typescript
|
||||
const instance = new MyDataModel(1, 'value');
|
||||
const folded = instance.foldToJson(); // Serialize to JSON
|
||||
console.log(folded);
|
||||
// Create and serialize
|
||||
const user = new User('john_doe', 'john@example.com');
|
||||
user.settings.theme = 'dark';
|
||||
|
||||
const enfoldedInstance = MyDataModel.enfoldFromJson(folded); // Deserialize back to instance
|
||||
console.log(enfoldedInstance);
|
||||
```
|
||||
// Convert to JSON
|
||||
const jsonString = user.foldToJson();
|
||||
console.log(jsonString);
|
||||
// {"username":"john_doe","email":"john@example.com","settings":{"theme":"dark","notifications":true}}
|
||||
|
||||
### Deep Comparison
|
||||
|
||||
`@push.rocks/smartjson` enables deep comparison of objects to determine if they are equivalent.
|
||||
|
||||
```typescript
|
||||
const obj1 = { a: 1, b: { c: 2 }};
|
||||
const obj2 = { a: 1, b: { c: 2 }};
|
||||
|
||||
const isEqual = smartjson.deepEqualObjects(obj1, obj2);
|
||||
console.log(isEqual); // true
|
||||
// Restore from JSON with correct typing
|
||||
const restoredUser = User.enfoldFromJson(jsonString);
|
||||
console.log(restoredUser instanceof User); // true
|
||||
console.log(restoredUser.settings instanceof UserSettings); // true
|
||||
```
|
||||
|
||||
### Handling Buffers and Typed Arrays
|
||||
|
||||
`smartjson` transparently handles JavaScript's `Buffer` and Typed Arrays during JSON serialization, making it effortless to work with binary data in JSON format.
|
||||
### Working with Nested Objects
|
||||
|
||||
```typescript
|
||||
const buffer = new Uint8Array([1, 2, 3]);
|
||||
const objWithBuffer = { key: buffer };
|
||||
const serialized = smartjson.stringify(objWithBuffer);
|
||||
class Company extends Smartjson {
|
||||
@foldDec() public name: string;
|
||||
@foldDec() public employees: Employee[] = [];
|
||||
|
||||
addEmployee(employee: Employee) {
|
||||
this.employees.push(employee);
|
||||
}
|
||||
}
|
||||
|
||||
const deserialized = smartjson.parse(serialized);
|
||||
console.log(deserialized.key); // Instance of Uint8Array or Buffer
|
||||
class Employee extends Smartjson {
|
||||
@foldDec() public name: string;
|
||||
@foldDec() public role: string;
|
||||
@foldDec() public salary: number;
|
||||
|
||||
constructor(name: string, role: string, salary: number) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.role = role;
|
||||
this.salary = salary;
|
||||
}
|
||||
}
|
||||
|
||||
const company = new Company();
|
||||
company.name = 'TechCorp';
|
||||
company.addEmployee(new Employee('Alice', 'Developer', 100000));
|
||||
company.addEmployee(new Employee('Bob', 'Designer', 90000));
|
||||
|
||||
// Serialize entire object graph
|
||||
const json = company.foldToJson();
|
||||
|
||||
// Deserialize with all nested objects properly instantiated
|
||||
const restored = Company.enfoldFromJson(json);
|
||||
```
|
||||
|
||||
In addition to these features, `@push.rocks/smartjson` supports efficient base64 encoding/decoding, deep object comparison, and JSON Lines parsing, making it a versatile library for dealing with JSON in TypeScript projects.
|
||||
## Buffer and Binary Data Handling
|
||||
|
||||
For further information and more detailed examples, referring to the API documentation and the source code on [GitLab](https://gitlab.com/push.rocks/smartjson) can provide deeper insights into `@push.rocks/smartjson`'s capabilities.
|
||||
SmartJson seamlessly handles binary data in JSON:
|
||||
|
||||
```typescript
|
||||
// Automatic Buffer handling
|
||||
const dataWithBuffer = {
|
||||
name: 'BinaryData',
|
||||
buffer: Buffer.from('Hello World'),
|
||||
typedArray: new Uint8Array([1, 2, 3, 4, 5])
|
||||
};
|
||||
|
||||
// Stringify (buffers are automatically encoded)
|
||||
const jsonStr = smartjson.stringify(dataWithBuffer);
|
||||
|
||||
// Parse (buffers are automatically restored)
|
||||
const restored = smartjson.parse(jsonStr);
|
||||
console.log(restored.buffer); // Buffer
|
||||
console.log(restored.typedArray); // Uint8Array
|
||||
```
|
||||
|
||||
## Deep Comparison
|
||||
|
||||
Compare complex objects with automatic normalization:
|
||||
|
||||
```typescript
|
||||
// Deep object comparison
|
||||
const obj1 = {
|
||||
nested: {
|
||||
array: [1, 2, { deep: 'value' }],
|
||||
flag: true
|
||||
},
|
||||
name: 'test'
|
||||
};
|
||||
|
||||
const obj2 = {
|
||||
name: 'test', // Different order
|
||||
nested: {
|
||||
flag: true, // Different order
|
||||
array: [1, 2, { deep: 'value' }]
|
||||
}
|
||||
};
|
||||
|
||||
const isEqual = smartjson.deepEqualObjects(obj1, obj2); // true
|
||||
```
|
||||
|
||||
## Real-World Examples
|
||||
|
||||
### API Response Caching
|
||||
|
||||
```typescript
|
||||
class CachedAPIResponse extends Smartjson {
|
||||
@foldDec() public data: any;
|
||||
@foldDec() public timestamp: number;
|
||||
@foldDec() public endpoint: string;
|
||||
|
||||
isExpired(maxAge: number = 3600000): boolean {
|
||||
return Date.now() - this.timestamp > maxAge;
|
||||
}
|
||||
|
||||
static fromAPICall(endpoint: string, data: any): CachedAPIResponse {
|
||||
const response = new CachedAPIResponse();
|
||||
response.endpoint = endpoint;
|
||||
response.data = data;
|
||||
response.timestamp = Date.now();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// Store API response
|
||||
const apiData = await fetch('/api/users');
|
||||
const cached = CachedAPIResponse.fromAPICall('/api/users', await apiData.json());
|
||||
localStorage.setItem('cached_users', cached.foldToJson());
|
||||
|
||||
// Retrieve and check
|
||||
const stored = localStorage.getItem('cached_users');
|
||||
if (stored) {
|
||||
const cached = CachedAPIResponse.enfoldFromJson(stored);
|
||||
if (!cached.isExpired()) {
|
||||
return cached.data; // Use cached data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Management
|
||||
|
||||
```typescript
|
||||
class AppConfig extends Smartjson {
|
||||
@foldDec() public apiUrl: string;
|
||||
@foldDec() public features: Map<string, boolean> = new Map();
|
||||
@foldDec() public limits: {
|
||||
maxUploadSize: number;
|
||||
maxConcurrentRequests: number;
|
||||
};
|
||||
|
||||
enableFeature(name: string) {
|
||||
this.features.set(name, true);
|
||||
}
|
||||
|
||||
save() {
|
||||
fs.writeFileSync('config.json', this.foldToJson());
|
||||
}
|
||||
|
||||
static load(): AppConfig {
|
||||
const json = fs.readFileSync('config.json', 'utf-8');
|
||||
return AppConfig.enfoldFromJson(json);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Core Functions
|
||||
|
||||
- `parse(jsonString: string): any` - Parse JSON with automatic buffer handling
|
||||
- `stringify(obj: any, simpleOrderArray?: string[], options?: Options): string` - Convert to JSON with stable ordering
|
||||
- `stringifyPretty(obj: any): string` - Pretty print JSON with 2-space indentation
|
||||
- `stringifyBase64(obj: any): string` - Encode JSON as base64
|
||||
- `parseBase64(base64String: string): any` - Decode base64 JSON
|
||||
- `parseJsonL(jsonLinesString: string): any[]` - Parse JSON Lines format
|
||||
- `deepEqualObjects(obj1: any, obj2: any): boolean` - Deep comparison of objects
|
||||
- `deepEqualJsonLStrings(jsonL1: string, jsonL2: string): boolean` - Compare JSON Lines strings
|
||||
|
||||
### Smartjson Class
|
||||
|
||||
- `Smartjson.enfoldFromObject<T>(obj: any): T` - Create instance from plain object
|
||||
- `Smartjson.enfoldFromJson<T>(json: string): T` - Create instance from JSON string
|
||||
- `instance.foldToObject(): any` - Convert instance to plain object
|
||||
- `instance.foldToJson(): string` - Convert instance to JSON string
|
||||
|
||||
### Decorators
|
||||
|
||||
- `@foldDec()` - Mark class property for serialization
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Use stable stringification** for consistent hashing and comparison
|
||||
2. **Enable pretty printing** only for debugging (it's slower)
|
||||
3. **Cache base64 encodings** when repeatedly sending the same data
|
||||
4. **Use JSON Lines** for streaming large datasets
|
||||
5. **Avoid circular references** in objects being serialized
|
||||
|
||||
## Migration Guide
|
||||
|
||||
If you're migrating from native JSON:
|
||||
|
||||
```typescript
|
||||
// Before
|
||||
JSON.parse(jsonString);
|
||||
JSON.stringify(object);
|
||||
|
||||
// After
|
||||
smartjson.parse(jsonString); // Adds buffer support
|
||||
smartjson.stringify(object); // Adds stable ordering & buffer support
|
||||
```
|
||||
|
||||
## Browser Support
|
||||
|
||||
This library supports modern browsers and Node.js environments. For older browsers, ensure you have appropriate polyfills for `TextEncoder` and `TextDecoder`.
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
@@ -138,4 +365,4 @@ Registered at District court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
Reference in New Issue
Block a user