mathers
v1.0.0
Published
Go-compatible JSON encoder - byte-for-byte compatible with encoding/json.Marshal
Maintainers
Readme
mathers
Perfect Go-compatible JSON encoder with type-driven behavior that mirrors Go's encoding/json.Marshal exactly.
Overview
mathers provides byte-for-byte compatibility with Go's JSON marshaling by implementing type-driven behavior that distinguishes between Go structs and Go maps. This fundamental distinction allows JavaScript developers to produce identical JSON output to their Go counterparts.
Key Innovation: Type-Driven JSON Marshaling
Just as Go treats structs and maps differently during JSON marshaling, mathers provides distinct JavaScript constructs:
- Plain objects → Go struct behavior (preserve field order)
map()function → Go map behavior (sort keys alphabetically)- ES6 Maps → Go map behavior (sort keys alphabetically)
Installation
npm install mathersQuick Start
import { marshal, map } from 'mathers';
// Plain objects preserve insertion order (Go struct behavior)
const person = { id: 1, name: "Alice", age: 30 };
marshal(person); // {"id":1,"name":"Alice","age":30}
// map() function sorts keys alphabetically (Go map behavior)
const settings = map({ zebra: "last", alpha: "first", beta: "middle" });
marshal(settings); // {"alpha":"first","beta":"middle","zebra":"last"}API Reference
Functions
marshal(value: GoJSON): string
Encodes a JavaScript value to Go-compatible JSON string with type-driven behavior.
Parameters:
value: GoJSON- The value to encode
Returns:
string- JSON string that is byte-for-byte compatible with Go'sencoding/json.Marshal
Examples:
import { marshal } from 'mathers';
// Struct-like data (preserves order)
marshal({ name: "Alice", age: 30 });
// → '{"name":"Alice","age":30}'
// Array data
marshal([1, 2, 3]);
// → '[1,2,3]'map(obj: Record<string, GoJSON>): GoMap
Creates a map object that will have its keys sorted alphabetically during marshaling.
Parameters:
obj: Record<string, GoJSON>- Plain object to convert to sortable map
Returns:
GoMap- Object that marshals with alphabetically sorted keys
Examples:
import { marshal, map } from 'mathers';
// Create map with sorted keys
const config = map({
database: "postgres",
cache: "redis",
queue: "rabbitmq"
});
marshal(config);
// → '{"cache":"redis","database":"postgres","queue":"rabbitmq"}'Type Definitions
export type GoJSON =
| null
| boolean
| number
| string
| GoJSON[]
| GoMap;
export interface GoMap {
readonly [k: string]: GoJSON;
}Go Struct vs Map Behavior
Go Struct Behavior (Plain Objects)
Plain JavaScript objects preserve their field insertion order, matching how Go marshals struct fields:
// Go equivalent: type Person struct { ID int; Name string; Age int }
const person = {
id: 1,
name: "Alice",
age: 30
};
marshal(person); // {"id":1,"name":"Alice","age":30}
// Preserves declaration order like Go structsGo Map Behavior (Sorted Keys)
Use map() function or ES6 Maps for sorted keys, matching Go's map[string]T behavior:
map() Function Objects
// Go equivalent: map[string]string{"zebra": "last", "alpha": "first"}
const settings = map({
zebra: "last",
alpha: "first",
beta: "middle"
});
marshal(settings); // {"alpha":"first","beta":"middle","zebra":"last"}ES6 Maps
const userRoles = new Map([
["zebra", "admin"],
["alpha", "user"],
["beta", "guest"]
]);
marshal(userRoles); // {"alpha":"user","beta":"guest","zebra":"admin"}Error Handling
mathers enforces strict Go compatibility by rejecting invalid inputs:
// Invalid numbers (Go rejects these)
marshal(NaN); // Error: NaN/Infinity not supported by Go JSON
marshal(Infinity); // Error: NaN/Infinity not supported by Go JSON
// Undefined values (Go omits these)
marshal({ key: undefined }); // Error: Undefined value for key: key
// Unsupported types
marshal(Symbol('test')); // Error: Unsupported type: symbolHTML Safety and Character Escaping
mathers escapes characters exactly like Go for security and compatibility:
const htmlContent = {
script: '<script>alert("XSS")</script>',
html: '<div>Content & more</div>'
};
marshal(htmlContent);
// {"script":"\u003cscript\u003ealert(\"XSS\")\u003c/script\u003e","html":"\u003cdiv\u003eContent \u0026 more\u003c/div\u003e"}Escaped Characters:
- HTML:
<→\u003c,>→\u003e,&→\u0026 - Control:
\b,\f,\n,\r,\t - Unicode line separators:
\u2028,\u2029 - Special:
"→\",\→\\
Go Compatibility Verification
This library maintains byte-for-byte compatibility with Go's encoding/json.Marshal through:
Verification Process
- Go test generation: 36 test files created by actual Go code
- Automated comparison: Every output compared against Go's output
- Edge case coverage: Special numbers, Unicode, HTML, deep nesting
- Continuous testing: CI runs Go compatibility tests on every change
Compatibility Features
| Feature | mathers | Go json.Marshal |
|---------|---------|-------------------|
| Struct field order | Preserved | Preserved |
| Map key order | Alphabetically sorted | Alphabetically sorted |
| HTML escaping | \u003c, \u003e, \u0026 | \u003c, \u003e, \u0026 |
| Negative zero | Preserved as -0 | Preserved as -0 |
| NaN/Infinity | Throws error | Returns error |
| Compact output | No spaces | No spaces |
Running Verification
# Generate Go test vectors
cd test && go run generate-test-files.go
# Run compatibility tests
npm test test/go-file-comparison.test.tsComparison with Standard JSON
| Feature | mathers | JSON.stringify |
|---------|-----------|------------------|
| Object key order | Type-driven (preserve/sort) | Insertion order |
| Map support | Native ES6 Map support | Requires replacer function |
| HTML safety | Always escaped | No escaping |
| Invalid numbers | Throws error | Converts to null |
| Undefined handling | Throws error | Omits or null |
| Go compatibility | Byte-for-byte identical | Different output |
Development
Prerequisites
- Node.js 18+
- Go 1.19+ (for compatibility testing)
Setup
# Install dependencies
npm install
# Build the library
npm run build
# Run tests with coverage
npm run test:coverage
# Run mutation testing (85%+ required)
npm run test:mutation
# Full verification pipeline
npm run verifyTesting Standards
- 100% line coverage maintained
- 85%+ mutation testing score required
- Property-based testing with fast-check
- Go compatibility tests for every release
Verification Commands
# Generate Go test vectors
cd test && go run generate-test-files.go
# Verify against Go output
npm test test/go-file-comparison.test.ts
# Full verification pipeline
npm run verifyContributing
We welcome contributions! Please ensure:
- All tests pass:
npm run verify - Coverage maintained: 100% line coverage required
- Go compatibility: All compatibility tests must pass
- Ultra-descriptive naming: No abbreviations
- Exhaustive testing: Property-based and edge case testing
Code Standards
// ✅ Ultra-descriptive names
function encodeGoCompatibleJSON(value: GoJSON): string
const shouldSortObjectKeysAlphabetically = true;
// ❌ Abbreviations and unclear names
function encode(val: any): string
const sort = true;Development Philosophy
mathers follows strict empirical engineering principles:
- Ultra-descriptive names - No abbreviations
- Exhaustive testing - Every change must increase or maintain coverage
- Go compatibility - All outputs must match Go's
encoding/json.Marshalexactly - Type safety - Full TypeScript strict mode compliance
Changelog
[1.0.0] - 2024-01-15
Added:
- Type-driven JSON marshaling with distinct behavior for Go structs vs maps
marshal(value)function for Go-compatible JSON encodingmap(obj)function for creating objects with sorted keys- Full ES6 Map support with automatic key sorting
- Byte-for-byte Go compatibility verified against
encoding/json.Marshal - Comprehensive error handling matching Go's validation rules
- HTML safety with proper character escaping
- TypeScript support with strict type checking
- Zero-dependency implementation
Security:
- Fixed null options parameter vulnerability
- XSS prevention through HTML character escaping
- Safe handling of undefined values with clear error messages
Testing:
- 100% line coverage maintained
- 85%+ mutation testing score
- 36 Go compatibility test files generated and verified
- 170+ total tests covering all functionality
License
MIT
Why "mathers"?
Named after Marshal mathers (Eminem), because this library marshals data with the precision and attention to detail of a master craftsman. Just as the artist's lyrics are meticulously structured, this encoder ensures every byte matches Go's output exactly.
