UNPKG

@ffsm/serialize

Version:
591 lines (461 loc) 17.8 kB
# Serialization string ## Installation ```bash npm i @ffsm/serialize ``` OR ```bash yarn add @ffsm/serialize ``` ## Usage Import and use as a named function: ```typescript import { encode } from '@ffsm/serialize`; encode('&'); ``` OR import and use from the default export: ```typescript import Serialize from '@ffsm/serialize'; Serialize.encode('&'); ``` ### decode Safely decodes URL-encoded strings with error handling. ```typescript import { decode } from '@ffsm/serialize'; // Basic usage - decodes with decodeURIComponent (default) const text = decode('Hello%20World'); // 'Hello World' // Use decodeURI instead (preserves certain URL characters) const url = decode('https%3A%2F%2Fexample.com%2Fpath%3Fquery%3Dvalue', false); // 'https://example.com/path?query=value' // Custom decoder function const emoji = decode('%F0%9F%8C%9F', myCustomDecoder); // Graceful error handling const problematic = decode('This%20has%2G%an%20error'); // Returns with best-effort decoding, logs error but doesn't throw exception ``` The function handles null and undefined values gracefully, returning an empty string. If decoding fails for certain segments, it maintains the original encoded value and logs an error to the console rather than throwing exceptions. ### encode Safely encodes strings for URL usage with error handling. ```typescript import { encode } from '@ffsm/serialize'; // Basic usage - encodes with encodeURIComponent (default) const encoded = encode('Hello World'); // 'Hello%20World' // Use encodeURI instead (preserves URL structure characters) const urlSafe = encode('https://example.com/path?query=value', false); // 'https://example.com/path?query=value' // Custom encoder function const custom = encode('Special chars: @#$%', myCustomEncoder); // Graceful error handling for problematic strings const safe = encode('Text with invalid \uD800 surrogate character'); // Handles the encoding without throwing exceptions ``` The function returns an empty string for null or undefined inputs. If encoding encounters errors with certain characters, it maintains those characters as-is and logs an error to the console rather than throwing exceptions, ensuring your application continues to run smoothly. ### get Safely access nested properties in objects and arrays without causing errors. ```typescript import { get } from '@ffsm/serialize'; const user = { name: 'John', profile: { contact: { email: 'john@example.com', phone: null, }, preferences: ['dark-mode', 'notifications'], }, }; // Simple property access const name = get(user, 'name'); // 'John' // Deep property access (as string path) const email = get(user, 'profile.contact.email'); // 'john@example.com' // Deep property access (as array path) const email2 = get(user, ['profile', 'contact', 'email']); // 'john@example.com' // Array element access const pref = get(user, 'profile.preferences.0'); // 'dark-mode' // Providing default values when property doesn't exist const address = get(user, 'profile.contact.address', 'N/A'); // 'N/A' // Type safety with generics const phone = get<string>(user, 'profile.contact.phone', 'No phone'); // 'No phone' ``` This function helps you avoid the infamous "Cannot read property 'x' of undefined" errors by safely navigating through object hierarchies. It works with both dot notation strings and array paths for flexibility. ### parse Converts a URL query string into a structured object with support for various formats. ```typescript import { parse } from '@ffsm/serialize'; // Basic parsing const params = parse('name=John&age=30'); // { name: 'John', age: '30' } // Auto-convert types const typedParams = parse('active=true&count=42', { parseBooleans: true, parseNumbers: true, }); // { active: true, count: 42 } // Parse arrays in different formats const bracketArray = parse('colors[]=red&colors[]=blue', { arrayFormat: 'bracket', }); // { colors: ['red', 'blue'] } const indexArray = parse('colors[0]=red&colors[1]=blue', { arrayFormat: 'index', }); // { colors: ['red', 'blue'] } const commaArray = parse('colors=red,blue', { arrayFormat: 'comma' }); // { colors: ['red', 'blue'] } // Parse nested objects const nested = parse('user[name]=John&user[profile][age]=30'); // { user: { name: 'John', profile: { age: '30' } } } // Parsing from URL with query prefix const fromUrl = parse('?sort=desc&page=2', { ignoreQueryPrefix: true }); // { sort: 'desc', page: '2' } ``` #### Options Configure parsing behavior with `SerializeParseOptions`: | Option | Type | Default | Description | | ---------------------- | ------- | ------- | --------------------------------------------------------------------------- | | `arrayFormat` | string | 'none' | How arrays are formatted ('bracket', 'index', 'comma', 'separator', 'none') | | `arrayFormatSeparator` | string | ',' | Character separating array values when using 'comma' or 'separator' | | `parseNumbers` | boolean | false | Auto-convert numeric strings to numbers | | `parseBooleans` | boolean | false | Auto-convert 'true'/'false' strings to booleans | | `decode` | boolean | true | Whether to decode URI encoded components | | `ignoreQueryPrefix` | boolean | true | Whether to remove '?' prefix from query string | ### query Converts a JavaScript object into a URL query string with support for various formats. ```typescript import { query } from '@ffsm/serialize'; // Basic object to query string const basic = query({ name: 'John', age: 30 }); // 'name=John&age=30' // Format arrays in different ways const bracketArray = query( { colors: ['red', 'blue'] }, { arrayFormat: 'bracket', } ); // 'colors[]=red&colors[]=blue' const indexArray = query( { colors: ['red', 'blue'] }, { arrayFormat: 'index', } ); // 'colors[0]=red&colors[1]=blue' const commaArray = query( { colors: ['red', 'blue'] }, { arrayFormat: 'comma', } ); // 'colors=red,blue' // Custom array separator const customSep = query( { tags: ['javascript', 'typescript'] }, { arrayFormat: 'separator', arrayFormatSeparator: '|', } ); // 'tags=javascript|typescript' // Handle nested objects const nested = query({ user: { name: 'John', profile: { age: 30 }, }, }); // 'user[name]=John&user[profile][age]=30' // Skip null or undefined values const skipNulls = query({ name: 'John', email: null }, { skipNull: true }); // 'name=John' // Skip empty strings const skipEmpty = query({ name: 'John', bio: '' }, { skipEmptyString: true }); // 'name=John' // Sort parameters const sorted = query({ z: 3, a: 1, b: 2 }, { sort: true }); // 'a=1&b=2&z=3' // Custom sorting function const customSort = query( { z: 3, a: 1, b: 2 }, { sort: (a, b) => b.localeCompare(a), // reverse sort } ); // 'z=3&b=2&a=1' ``` #### Options Configure serialization behavior with `SerializeQueryOptions`: | Option | Type | Default | Description | | ---------------------- | ---------------- | ------- | --------------------------------------------------------------------------- | | `arrayFormat` | string | 'none' | How arrays are formatted ('bracket', 'index', 'comma', 'separator', 'none') | | `arrayFormatSeparator` | string | ',' | Character separating array values when using 'comma' or 'separator' | | `skipNull` | boolean | false | Whether to omit null and undefined values | | `skipEmptyString` | boolean | false | Whether to omit empty string values | | `encode` | boolean | true | Whether to encode URI components | | `strict` | boolean | true | Whether to validate keys strictly | | `sort` | boolean/function | false | Whether and how to sort the parameters | ### url Replaces named parameters in a URL template with actual values. ```typescript import { url } from '@ffsm/serialize'; // Simple parameter replacement const profileUrl = url('/users/:username', { username: 'john.doe' }); // '/users/john.doe' // Multiple parameters in a route const articleUrl = url('/blog/:category/:slug/:id', { category: 'technology', slug: 'javascript-tips', id: 42, }); // '/blog/technology/javascript-tips/42' // Same parameter used multiple times const duplicateParams = url('/products/:id/reviews/:reviewId/by/:id', { id: 'abc123', reviewId: 789, }); // '/products/abc123/reviews/789/by/abc123' // With primitive types const apiUrl = url('/api/:version/filter/:active/:count', { version: 'v2', active: true, count: 50, }); // '/api/v2/filter/true/50' // Missing parameters are replaced with empty string const partialUrl = url('/users/:id/posts/:postId', { id: 123 }); // '/users/123/posts/' // No parameters to replace const staticUrl = url('/about-us', {}); // '/about-us' ``` The function provides a simple way to build URLs with dynamic parameters, similar to how routing works in modern web frameworks. It safely handles different primitive types (string, number, boolean) and replaces missing parameters with empty strings rather than throwing errors. ### variable Replaces variable placeholders in a string template with actual values. ```typescript import { variable } from '@ffsm/serialize'; // Basic template substitution const greeting = variable('Hello, {name}!', { params: { name: 'John' }, }); // 'Hello, John!' // Multiple variables const userInfo = variable('Name: {name}, Email: {email}, Role: {role}', { params: { name: 'Alice', email: 'alice@example.com', role: 'Admin', }, }); // 'Name: Alice, Email: alice@example.com, Role: Admin' // Accessing nested properties with dot notation const profileText = variable('Profile: {user.name} ({user.details.age})', { params: { user: { name: 'Bob', details: { age: 32, location: 'New York', }, }, }, }); // 'Profile: Bob (32)' // Custom formatting const eventDetails = variable('Event: {title} on {date}', { params: { title: 'Company Meeting', date: '2023-09-15', }, format: { title: (title) => title.toUpperCase(), date: (date) => { const [year, month, day] = date.split('-'); return `${month}/${day}/${year}`; }, }, }); // 'Event: COMPANY MEETING on 09/15/2023' // Array values const listItems = variable('Selected items: {items.0}, {items.1}, {items.2}', { params: { items: ['Apple', 'Banana', 'Cherry'], }, }); // 'Selected items: Apple, Banana, Cherry' ``` The function provides a flexible template system for string interpolation with: - Variable substitution with `{variableName}` syntax - Deep property access with dot notation: `{user.profile.name}` - Custom formatting through synchronous formatter functions - Multiple occurrences of the same variable - Safe handling of missing variables (replaced with empty string) For asynchronous operations (API calls, database queries), use `variableAsync`. ### variableAsync Asynchronously replaces variable placeholders in a string template with actual values, supporting async formatters. ```typescript import { variableAsync } from '@ffsm/serialize'; // Basic template substitution (similar to variable) const greeting = await variableAsync('Hello, {name}!', { params: { name: 'John' }, }); // 'Hello, John!' // With async formatters const userProfile = await variableAsync('User: {userId}', { params: { userId: 1234 }, format: { userId: async (id) => { // Fetch user data from API const response = await fetch(`/api/users/${id}`); const user = await response.json(); return `${user.name} (${user.status})`; }, }, }); // 'User: John Doe (active)' // Multiple async formatters processed in parallel const weatherReport = await variableAsync( 'Weather: {city1} | {city2} | {city3}', { params: { city1: 'New York', city2: 'London', city3: 'Tokyo', }, format: { city1: async (city) => await fetchWeatherData(city), city2: async (city) => await fetchWeatherData(city), city3: async (city) => await fetchWeatherData(city), }, } ); // All three weather API calls happen concurrently // Formatted date with timezone adjustment const eventTime = await variableAsync('Event starts: {startTime}', { params: { startTime: '2023-09-15T14:00:00Z' }, format: { startTime: async (time) => { // Could involve timezone API call or complex async calculation const localTime = await convertToLocalTimezone(time); return localTime.toLocaleString(); }, }, }); // 'Event starts: 9/15/2023, 10:00:00 AM' ``` #### When to use variableAsync vs variable Use `variableAsync` when you need: - **Async data sources**: Fetching data from APIs, databases, or file systems - **Complex transformations**: Processing that requires promises or async/await - **Parallel processing**: All variables are processed concurrently for better performance - **External services**: Integration with services requiring network requests Use `variable` (synchronous version) for simpler cases where: - You only need basic variable substitution - All data is already available in memory - Performance is critical and async overhead isn't needed `variableAsync` provides all the same templating features as `variable`, plus: - Support for async formatter functions - Parallel processing of multiple variables using Promise.all - Seamless integration with async/await workflows ### isString Checks if a value is a string with TypeScript type guard support. ```typescript import { isString } from '@ffsm/serialize'; // Basic usage const value = getUserInput(); if (isString(value)) { // TypeScript knows value is a string here const normalized = value.trim().toLowerCase(); console.log(`Processing string: ${normalized}`); } else { console.log('Expected a string value'); } // Using with mapping functions const processedValues = mixedData .filter(isString) .map((str) => str.toUpperCase()); // processedValues is string[] ``` This function not only checks if a value is a string at runtime but also serves as a TypeScript type guard, providing type narrowing in conditional blocks for improved type safety. ### isNumber Checks if a value is a number with TypeScript type guard support. ```typescript import { isNumber } from '@ffsm/serialize'; // Basic usage const value = getValueFromAPI(); if (isNumber(value)) { // TypeScript knows value is a number here const formatted = value.toFixed(2); console.log(`Numeric value: ${formatted}`); } else { console.log('Expected a numeric value'); } // In data processing pipelines const numericValues = dataset.filter(isNumber).filter((num) => num > 0); // numericValues is number[] // For calculations function calculateAverage(values: unknown[]) { const numbers = values.filter(isNumber); if (numbers.length === 0) return 0; return numbers.reduce((sum, num) => sum + num, 0) / numbers.length; } ``` This function checks if a value is a number at runtime while also serving as a TypeScript type guard, allowing you to safely access number methods in conditional blocks. ### isBoolean Checks if a value is a boolean with TypeScript type guard support. ```typescript import { isBoolean } from '@ffsm/serialize'; // Basic usage const value = getConfigValue('featureEnabled'); if (isBoolean(value)) { // TypeScript knows value is a boolean here if (value) { enableFeature(); } else { disableFeature(); } } else { console.log('Expected a boolean configuration value'); } // With optional chaining function getFeatureState(config: unknown) { if (isBoolean(config?.features?.darkMode)) { return config.features.darkMode ? 'enabled' : 'disabled'; } return 'not configured'; } // Filtering boolean flags const enabledFeatures = Object.entries(featureFlags) .filter(([_, value]) => isBoolean(value) && value) .map(([key]) => key); ``` This function verifies a value is strictly a boolean (true or false) at runtime while also acting as a TypeScript type guard, making it safer to use in conditional logic. ### isPrimitive Checks if a value is a primitive (string, number, boolean, null, or undefined) with TypeScript type guard support. ```typescript import { isPrimitive } from '@ffsm/serialize'; // Basic usage const value = getSomeValue(); if (isPrimitive(value)) { // TypeScript knows value is a string, number, boolean, null, or undefined here console.log(`Simple value: ${String(value)}`); } else { // TypeScript knows value is an object or array here console.log('Complex data structure'); } // Differentiating between primitive and complex values function serializeValue(value: unknown) { if (isPrimitive(value)) { return String(value); // Simple stringification for primitives } else { return JSON.stringify(value); // JSON for objects and arrays } } // Validating input types function validateInput(input: unknown) { if (!isPrimitive(input)) { throw new Error('Only primitive values are accepted'); } return input; // TypeScript knows input is a primitive here } ``` This function helps distinguish between simple primitive values and complex data structures (objects and arrays) both at runtime and at compile time with TypeScript.