A few weeks ago, I introduced handlejson with v0.2.0 - a simple library to eliminate try-catch spam when parsing JSON. After development from December 2025 to January 2026 and 244 tests, v1.0.0 is production-ready.
The Journey to v1.0.0
When I first released handlejson, it solved a real problem - eliminating try-catch boilerplate. Fast forward to today, and it's production-ready with comprehensive tests, performance improvements, and enterprise-grade features.
What Changed Since v0.2.0
The core idea remains the same - a focused solution that returns null on error instead of throwing. Here's what's been added:
v0.3.0 (January 10)
-
Better validation errors:
tryValidate()now returns detailed error info (path, expected, actual) -
Optional fields: Prefix types with
?to mark fields as optional (age: '?number') - Array validation: Validate array item types, not just array presence
- CI/CD: GitHub Actions workflows for automated testing
v1.0.0 (January 30) - Production Ready
-
244 comprehensive tests covering all edge cases
- Security features (maxSize, maxDepth, safeKeys)
- Date handling (ISO strings, timestamps)
- Circular reference handling
- Stream parsing for large files
- Schema validation with detailed errors
-
Performance improvements: 2-3x faster parsing (5.2M ops/s vs ~2M ops/s)
- Date regex compiled once instead of per call
- Smart object copying (only when dangerous keys found) - optimized key sanitization
-
Enhanced security: Improved
sanitizeKeysperformance for prototype pollution protection - Enhanced error messages: Better formatting with position and context
- Memory efficiency: Improved garbage collection behavior
- CI/CD: Automated testing across Node 18, 20, 22
- Documentation: Performance benchmarks and enhanced docs
How many times have you written this?
let data
try {
data = JSON.parse(str)
} catch {
data = null
}
If you're like me, probably dozens of times. Every API response, every localStorage read, every user input - same pattern, same boilerplate.
After writing this pattern one too many times, I built handlejson.
What is handlejson?
It's a focused wrapper around JSON.parse that returns null on error instead of throwing. This simple change eliminates the try-catch boilerplate you write everywhere.
import { parse } from 'handlejson'
const data = parse(str) // null if invalid
const data = parse(str, { default: {} }) // {} if invalid
Simple, but genuinely useful.
Why I built it
The breaking point was a project parsing JSON from APIs, localStorage, user input, and config files. Every single parse needed a try-catch. My code was cluttered with error handling boilerplate.
I checked existing solutions, but they were either too heavy, missing security features, or had dependencies I didn't want. So I built my own.
What makes it useful
1. Security options built-in
When handling untrusted input, you need protection. Most JSON libraries don't include this:
const safe = parse(userInput, {
maxSize: 10 * 1024 * 1024, // Prevent memory exhaustion attacks
maxDepth: 100, // Prevent stack overflow from deeply nested JSON
safeKeys: true // Block prototype pollution (__proto__, constructor, prototype)
})
These aren't theoretical - they're real attack vectors:
- maxSize: Prevents attackers from sending huge JSON payloads that exhaust memory
- maxDepth: Prevents deeply nested JSON that causes stack overflow
- safeKeys: Blocks prototype pollution attacks that can lead to code execution
Having them built-in saves you from implementing them yourself. In v1.0.0, the sanitizeKeys function was optimized to only copy objects when dangerous keys are actually found, improving performance.
2. Better error messages
When debugging JSON errors, knowing where the error is helps:
import { parseWithDetails } from 'handlejson'
const result = parseWithDetails('{"name":"John", invalid}')
if (!result.success) {
console.log(result.position) // 18
console.log(result.context) // Shows surrounding context
console.log(result.error) // Detailed error message
}
Much better than "Unexpected token" with no context.
3. Schema validation
Basic type checking without extra dependencies:
const schema = { name: 'string', age: 'number' }
const user = parse('{"name":"John","age":30}', { schema })
// Returns null if validation fails
What's new in v0.3.0+: Optional fields and better error messages:
const schema = {
name: 'string',
age: '?number', // Optional field
email: '?string' // Optional field
}
const [valid, error] = tryValidate(data, schema)
if (!valid) {
console.log(error.path) // 'age'
console.log(error.expected) // 'number'
console.log(error.actual) // 'string'
}
Perfect for validating API responses, configuration files, and user input without adding heavy dependencies. Handles optional fields, nested objects, and arrays with detailed error messages.
4. Stream parsing
For large files, parse in chunks:
import { parseStream } from 'handlejson'
const result = await parseStream(largeFileStream, {
onProgress: (parsed) => console.log('Progress:', parsed)
})
Handles files without loading everything into memory.
Performance
Benchmarks against native JSON:
- Small JSON (<1KB): 5.2M ops/s (vs ~6M for native)
- With security options: 3.4M ops/s
- Overhead exists, but minimal
For most applications, the convenience and security features are worth the minimal performance overhead. The 5.2M ops/s performance handles high-throughput scenarios while providing better error handling and built-in security.
Real-world use cases
API responses:
const response = await fetch('/api/user')
const user = parse(await response.text(), { default: {} })
// Always get an object, never crashes
LocalStorage:
const saved = parse(localStorage.getItem('data'), { default: null })
// Safe, no try-catch needed
User input with security:
const userData = parse(userInput, {
maxSize: 10 * 1024 * 1024,
maxDepth: 100,
safeKeys: true
})
When to use it
✅ Parsing API responses
✅ Handling user input
✅ Processing configuration files
✅ Debugging JSON errors
✅ Handling untrusted input (security options)
Why it matters
handlejson solves a problem every developer faces daily - repetitive try-catch boilerplate. Beyond convenience, it adds production-ready features like built-in security, detailed error messages, and comprehensive test coverage. The security options protect against real attack vectors, making it ideal for production applications handling untrusted input.
Production ready
After development from December 2025 to January 2026, v1.0.0 is ready:
- 244 tests covering edge cases (security, dates, circular refs, streams, validation)
- Zero dependencies
- 1.5KB gzipped
- TypeScript-first
- CI/CD with GitHub Actions (tests on Node 18, 20, 22)
- Performance: 5.2M ops/s for small JSON (vs ~6M for native)
The main difference from v0.2.0 is production-ready quality - comprehensive tests, performance optimizations, and better documentation. The API hasn't changed, so existing code works without modifications.
Try it out
npm install handlejson
import { parse, stringify } from 'handlejson'
const data = parse('{"name":"John"}') // { name: 'John' }
const json = stringify({ name: 'John' }) // '{"name":"John"}'
Links:
- npm: https://www.npmjs.com/package/handlejson
- GitHub: https://github.com/chintanshah35/handlejson
- Documentation: https://github.com/chintanshah35/handlejson#readme
If you're new to handlejson, check out my original introduction article where I covered the basics and v0.2.0 features.
Top comments (0)