Type-safe, immutable Jsonnet updates with local variable management, function definitions, comment preservation, and advanced array merging strategies.
- 🔒 Type-safe updates using TypeScript proxies for automatic path detection
- 🎯 Immutable operations - Original content never modified
- 📝 Comment preservation - Keep your documentation intact
- 📄 Document headers - Extract and preserve file headers with multiple formatting styles
- 🔧 Local variables - Manage
localdeclarations easily (similar to YAML anchors) - ⚡ Function definitions - Create and manage reusable Jsonnet functions
- 🔄 Advanced array merging - Multiple strategies (by name, by property, by content)
- 📐 Formatting preservation - Maintains indentation and style
- 🎨 Clean API - Intuitive, developer-friendly interface
npm install @hiscojs/jsonnet-updaterimport { updateJsonnet } from '@hiscojs/jsonnet-updater';
const jsonnetString = `
{
environment: 'dev',
replicas: 3,
image: 'myapp:1.0.0'
}
`;
const { result } = updateJsonnet({
jsonnetString,
annotate: ({ change }) => {
change({
findKey: (obj) => obj,
merge: (orig) => ({
...orig,
replicas: 5,
image: 'myapp:2.0.0'
})
});
}
});
console.log(result);
// Output:
// {
// environment: 'dev',
// replicas: 5,
// image: 'myapp:2.0.0'
// }The library uses a change function within annotate to specify updates. This provides type-safety and immutable updates:
annotate: ({ change }) => {
change({
findKey: (obj) => obj.parentObject, // Find the parent object to update
merge: (original) => ({ // Return updated parent object
...original, // Spread original properties
propertyToUpdate: newValue // Override specific properties
})
});
}Important: Always find the parent object containing the property you want to update, then return the complete updated parent object using the spread operator.
All operations return a JsonnetEdit<T> object:
interface JsonnetEdit<T> {
result: string; // Updated Jsonnet string
resultParsed: T; // Parsed object (evaluated Jsonnet)
originalParsed: T; // Original parsed object
locals: LocalVariable[]; // Defined local variables
functions: LocalFunction[]; // Defined local functions
}Local variables in Jsonnet are like YAML anchors - they allow you to define reusable values.
import { updateJsonnet } from '@hiscojs/jsonnet-updater';
const jsonnetString = `
{
name: 'myapp',
version: '1.0.0'
}
`;
const { result } = updateJsonnet({
jsonnetString,
locals: [
{
name: 'namespace',
value: 'production'
},
{
name: 'imageTag',
value: 'v2.0.0'
}
],
annotate: ({ change }) => {
change({
findKey: (obj) => obj.namespace,
merge: () => '$.namespace' // Reference the local variable
});
}
});
console.log(result);
// Output:
// local namespace = 'production';
// local imageTag = 'v2.0.0';
// {
// name: 'myapp',
// version: '1.0.0',
// namespace: $.namespace
// }const jsonnetString = `
local environment = 'dev';
local replicas = 3;
{
env: environment,
count: replicas
}
`;
const { result } = updateJsonnet({
jsonnetString,
locals: [
{ name: 'replicas', value: 5 } // Override the local variable
]
});
console.log(result);
// Output:
// local environment = 'dev';
// local replicas = 5; // <-- Updated
//
// {
// env: environment,
// count: replicas
// }Local functions provide reusable logic, similar to how you might use YAML anchors for complex structures.
import { updateJsonnet } from '@hiscojs/jsonnet-updater';
const jsonnetString = `
{
services: []
}
`;
const { result } = updateJsonnet({
jsonnetString,
functions: [
{
name: 'createService',
params: ['name', 'port'],
body: `{
name: name,
port: port,
protocol: 'TCP'
}`
}
],
annotate: ({ change, functions }) => {
change({
findKey: (obj) => obj.services,
merge: () => [
'$.createService("api", 8080)',
'$.createService("web", 3000)'
]
});
}
});
console.log(result);
// Output:
// local createService(name, port) = {
// name: name,
// port: port,
// protocol: 'TCP'
// };
//
// {
// services: [
// $.createService("api", 8080),
// $.createService("web", 3000)
// ]
// }const { result } = updateJsonnet({
jsonnetString: '{}',
functions: [
{
name: 'createDeployment',
params: ['name', 'image', 'replicas'],
body: `{
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: name
},
spec: {
replicas: replicas,
template: {
spec: {
containers: [{
name: name,
image: image
}]
}
}
}
}`
}
],
annotate: ({ change }) => {
change({
findKey: (obj) => obj.deployment,
merge: () => '$.createDeployment("myapp", "myapp:2.0", 3)'
});
}
});Use addInstructions for sophisticated array handling:
import { updateJsonnet, addInstructions } from '@hiscojs/jsonnet-updater';
const jsonnetString = `
{
services: [
{ name: 'api', port: 8080 },
{ name: 'web', port: 3000 }
]
}
`;
const { result } = updateJsonnet({
jsonnetString,
annotate: ({ change }) => {
change({
findKey: (obj) => obj.services,
merge: (current) => [
...current,
...addInstructions({
prop: 'services',
mergeByName: true // Merge by 'name' property
}),
{ name: 'api', port: 8081, replicas: 3 }, // Updates existing
{ name: 'cache', port: 6379 } // Adds new
]
});
}
});
console.log(result);
// Output:
// {
// services: [
// { name: 'api', port: 8081, replicas: 3 }, // Merged by name
// { name: 'web', port: 3000 },
// { name: 'cache', port: 6379 } // Added
// ]
// }...addInstructions({
prop: 'arrayName',
mergeByName: true, // Merge by 'name' property
// OR
mergeByProp: 'id', // Merge by specific property
// OR
mergeByContents: true, // Merge by full content comparison
deepMerge: true // Deep merge objects (default: false)
})Delete properties using the exclude helper function:
import { updateJsonnet, exclude } from '@hiscojs/jsonnet-updater';
const jsonnetString = `
{
name: 'myapp',
version: '1.0.0',
deprecated: true,
legacy: 'old-value'
}
`;
const { result } = updateJsonnet({
jsonnetString,
annotate: ({ change }) => {
change({
findKey: (obj) => obj,
merge: (orig) => exclude(orig, 'deprecated', 'legacy')
});
}
});
console.log(result);
// Output:
// {
// name: 'myapp',
// version: '1.0.0'
// }Combine exclude with the spread operator for partial updates:
const { result } = updateJsonnet({
jsonnetString,
annotate: ({ change }) => {
change({
findKey: (obj) => obj,
merge: (orig) => ({
...exclude(orig, 'deprecated'),
version: '2.0.0', // Update existing
environment: 'production' // Add new
})
});
}
});const { result } = updateJsonnet({
jsonnetString: `{
server: {
host: 'localhost',
port: 8080,
oldTimeout: 30
}
}`,
annotate: ({ change }) => {
change({
findKey: (obj) => obj.server,
merge: (orig) => ({
...exclude(orig, 'oldTimeout'),
timeout: 60 // Replace with new property
})
});
}
});Note: For deleting array elements, use standard JavaScript array methods like filter():
change({
findKey: (obj) => obj.items,
merge: (orig) => orig.filter(item => item.active)
});const jsonnetString = `
{
server: {
host: 'localhost',
port: 8080,
ssl: {
enabled: false
}
}
}
`;
const { result } = updateJsonnet({
jsonnetString,
annotate: ({ change }) => {
change({
findKey: (obj) => obj.server.ssl,
merge: (orig) => ({
...orig,
enabled: true,
cert: '/path/to/cert.pem'
})
});
}
});
console.log(result);
// Output:
// {
// server: {
// host: 'localhost',
// port: 8080,
// ssl: {
// enabled: true,
// cert: '/path/to/cert.pem'
// }
// }
// }Preserve and manage document headers (comments at the top of the file):
const jsonnetString = `# Application Configuration
# Version: 1.0
# Author: DevOps Team
{
name: 'myapp',
version: '1.0.0'
}`;
const { result, extractedHeader } = updateJsonnet({
jsonnetString,
documentHeader: {
type: 'simple',
content: ['Application Configuration', 'Version: 1.0', 'Author: DevOps Team']
},
annotate: ({ change }) => {
change({
findKey: (obj) => obj,
merge: (orig) => ({
...orig,
version: '2.0.0'
})
});
}
});
// Output:
// # Application Configuration
// # Version: 1.0
// # Author: DevOps Team
// {
// name: 'myapp',
// version: '2.0.0'
// }const { result } = updateJsonnet({
jsonnetString: '{ service: "api" }',
documentHeader: {
type: 'multi-line',
content: ['Service Configuration', 'Owner: Platform Team'],
border: '#',
width: 50
}
});
// Output:
// ##################################################
// # Service Configuration
// # Owner: Platform Team
// ##################################################
// {
// service: 'api'
// }const { result } = updateJsonnet({
jsonnetString: '{ generated: true }',
documentHeader: {
type: 'raw',
content: '// Custom header format\n// DO NOT EDIT - Generated file'
}
});
// Output:
// // Custom header format
// // DO NOT EDIT - Generated file
// {
// generated: true
// }When a documentHeader is provided, the library automatically extracts existing headers:
const { extractedHeader } = updateJsonnet({
jsonnetString: withHeader,
documentHeader: { type: 'simple', content: [] }
});
console.log(extractedHeader);
// {
// type: 'simple',
// content: ['Application Configuration', 'Version: 1.0'],
// raw: '# Application Configuration\n# Version: 1.0'
// }Control how the output is formatted:
const { result } = updateJsonnet({
jsonnetString,
formatOptions: {
indent: 2, // Number of spaces (or '\t')
preserveIndentation: true, // Auto-detect from source (default)
trailingNewline: true // Add newline at end (default)
},
annotate: ({ change }) => {
// Your updates...
}
});import { updateJsonnet } from '@hiscojs/jsonnet-updater';
const kubernetesTemplate = `
{
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
name: 'app-config'
},
data: {}
}
`;
const { result } = updateJsonnet({
jsonnetString: kubernetesTemplate,
locals: [
{ name: 'environment', value: 'production' },
{ name: 'region', value: 'us-west-2' }
],
annotate: ({ change }) => {
change({
findKey: (obj) => obj.metadata,
merge: (orig) => ({
...orig,
namespace: '$.environment'
})
});
change({
findKey: (obj) => obj,
merge: (orig) => ({
...orig,
data: {
DATABASE_URL: 'postgres://prod-db:5432',
REGION: '$.region',
LOG_LEVEL: 'info'
}
})
});
}
});const { result } = updateJsonnet({
jsonnetString: '{}',
functions: [
{
name: 'createService',
params: ['name', 'image', 'port', 'env'],
body: `{
name: name,
image: image,
ports: [{ containerPort: port }],
env: env
}`
}
],
locals: [
{ name: 'dbHost', value: 'postgres.example.com' },
{ name: 'cacheHost', value: 'redis.example.com' }
],
annotate: ({ change }) => {
change({
findKey: (obj) => obj.services,
merge: () => [
`$.createService(
"api",
"api:2.0",
8080,
[{ name: "DB_HOST", value: $.dbHost }]
)`,
`$.createService(
"worker",
"worker:1.5",
8081,
[
{ name: "DB_HOST", value: $.dbHost },
{ name: "CACHE_HOST", value: $.cacheHost }
]
)`
]
});
}
});Main function for updating Jsonnet content.
Options:
{
jsonnetString: string; // Input Jsonnet content
annotate?: (ctx: AnnotateContext) => void; // Update callback
formatOptions?: FormatOptions; // Formatting preferences
locals?: LocalVariable[]; // Local variable definitions
functions?: LocalFunction[]; // Local function definitions
documentHeader?: DocumentHeader; // Document header configuration
}Return Type:
interface JsonnetEdit<T> {
result: string; // Updated Jsonnet string
resultParsed: T; // Parsed updated object
originalParsed: T; // Original parsed object
locals: LocalVariable[]; // Defined local variables
functions: LocalFunction[]; // Defined local functions
extractedHeader?: ExtractedHeader; // Extracted document header (if present)
}AnnotateContext:
{
change: (instruction: ChangeInstruction) => void;
locals: Record<string, any>; // Access to local variables
functions: Record<string, Function>; // Access to local functions
}ChangeInstruction:
{
findKey: (proxy: T) => any; // Path selector with type-safety
merge: (current: any) => any; // Update function
}DocumentHeader:
{
type: 'simple' | 'multi-line' | 'raw'; // Header formatting style
content: string | string[]; // Header content
border?: string; // Border char for multi-line (default: '#')
width?: number; // Width for multi-line (default: auto)
}ExtractedHeader:
{
type: 'simple' | 'multi-line' | 'raw'; // Detected header type
content: string[]; // Parsed content lines
raw: string; // Raw header string
}Generate instructions for advanced array merging (re-exported from @hiscojs/object-updater).
Options:
{
prop: string; // Array property name
mergeByName?: boolean; // Merge by 'name' property
mergeByProp?: string; // Merge by specific property
mergeByContents?: boolean; // Merge by content comparison
deepMerge?: boolean; // Deep merge objects
}Helper function for deleting properties from objects (re-exported from @hiscojs/object-updater).
Parameters:
obj: The source objectkeys: Property names to exclude/delete (variable number of arguments)
Returns: A new object with specified properties set to undefined, signaling deletion
Example:
import { updateJsonnet, exclude } from '@hiscojs/jsonnet-updater';
// Delete single property
merge: (orig) => exclude(orig, 'deprecated')
// Delete multiple properties
merge: (orig) => exclude(orig, 'deprecated', 'legacy', 'old')
// Combine with spread for updates
merge: (orig) => ({
...exclude(orig, 'deprecated'),
version: '2.0.0',
newProp: 'value'
})Full TypeScript support with generic types:
interface MyConfig {
server: {
host: string;
port: number;
};
features: string[];
}
const { result, resultParsed } = updateJsonnet<MyConfig>({
jsonnetString,
annotate: ({ change }) => {
change({
findKey: (obj) => obj.server.port, // Type-safe!
merge: () => 9000
});
}
});
// resultParsed is typed as MyConfig
console.log(resultParsed.server.host);While jsonnet-updater is powerful for many use cases, it has some limitations:
- Cannot parse Jsonnet files with function calls in data values (e.g.,
person1: Person()) - Complex Jsonnet expressions may not parse correctly
- Best used with static JSON-like Jsonnet structures
- Functions are not evaluated (treated as templates)
- Function calls are represented as string references (
$.functionName())
- Comments are tracked but not fully re-inserted
- Basic comment preservation implementation
- Creating Jsonnet files from scratch with functions and local variables
- Updating static JSON-like Jsonnet structures with predictable schemas
- Managing local variables and functions as reusable templates
- Kubernetes manifest generation with templating
- Configuration file templating for multi-environment setups
- Complex Jsonnet with heavy use of function calls in data
- Files with computed values and conditionals that require evaluation
- Interactive Jsonnet evaluation or runtime value computation
| Feature | jsonnet-updater | yaml-updater | json-updater |
|---|---|---|---|
| Type-safe updates | ✅ | ✅ | ✅ |
| Comment preservation | ✅ | ✅ | ✅ |
| Local variables | ✅ (native) | ✅ (anchors) | ❌ |
| Functions | ✅ (native) | ❌ | ❌ |
| Array merging | ✅ | ✅ | ✅ |
| Multi-document | ❌ | ✅ | ❌ |
| Format detection | ✅ | ✅ | ✅ |
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
- @hiscojs/yaml-updater - YAML updates with comment preservation
- @hiscojs/json-updater - JSON/JSONC updates with formatting
- @hiscojs/dotenv-updater - .env file updates
- @hiscojs/object-updater - Core update logic engine