Quick answer: Run six JSON-to-YAML checks before deployment: preserve scalar types, retain mapping keys, compare multiline strings exactly, verify collection shape, control generated output, and validate application rules. Parse the emitted YAML with the receiving loader. Readable indentation is helpful, but it does not establish that the deployment will receive equivalent configuration.
A generated YAML file looks cleaner than its JSON source. Then a script gains a trailing newline, an identifier changes type, or a configuration field fails validation. The useful question is not whether the output looks like YAML. It is whether the receiving application gets the intended data.
The JSON-to-YAML converter offers indentation controls, quotes ambiguous strings, emits multiline text, and writes repeated values without aliases. It does not validate Kubernetes resources, workflow permissions, or an application's settings. For broader background, read JSON versus YAML and Wikipedia's YAML overview.
These six checks focus on outbound configuration. The separate YAML-to-JSON test guide covers incoming YAML parsing, including streams and aliases. Examples here were executed with the repository's installed JS-YAML 5.4.3. Run the relative imports from this repository's root after installing its dependencies; they exercise its conversion helper, not every YAML implementation.
1. Do strings retain their types in the receiving loader?
Emit a fixture containing numeric-looking identifiers, boolean-like words, date-like strings, empty text, and real booleans and numbers. Parse the YAML with the receiving loader and assert both values and types. Quoting decisions depend on schema compatibility, so a successful round trip through the emitting library alone is not a universal portability guarantee.
Write expectations before converting. A postal code is text even when every character is a digit. A port setting may intentionally be numeric. A release label that resembles a date should remain a label if the application expects one. The distinction belongs to the configuration contract, not the serializer's visual preferences.
The yaml package's schema documentation shows that loaders expose version and schema choices. Its option names are not interchangeable with JS-YAML's. Use the actual destination loader for acceptance; the local comparison below is a useful small regression test, not a substitute for that integration.
import assert from 'node:assert/strict';
import * as yaml from 'js-yaml';
import { jsonToYaml } from './lib/json-tools/convert.mjs';
const source = { country: 'NO', enabledText: 'true', code: '00123',
release: '2026-10-19', blank: '', enabled: true, retries: 3 };
const emitted = jsonToYaml(source);
for (const schema of [yaml.JSON_SCHEMA, yaml.YAML11_SCHEMA]) {
assert.deepEqual(yaml.load(emitted, { schema }), source);
}
This tests two configurations of one installed library. It does not claim that the consumer uses either configuration. Keep a production-loader test next to it, particularly when a build tool embeds a different parser or changes schema defaults during an upgrade.
For large identifiers, preserve the source as strings before JavaScript parses numeric JSON. A serializer cannot recover digits already rounded in memory. Our JSON number precision guide explains that earlier boundary. Test conversion with the values your application actually holds, not an idealized value that was lost before the serializer ran.
2. Do mapping keys survive without coercion or renaming?
Test ambiguous keys as carefully as ambiguous values. Include keys that look numeric or boolean-like, an empty key, and punctuation used by the application. Compare the exact parsed key set and associated values after emission. A configuration can retain every value yet become unusable when the loader changes the identity of its keys.
Keys are often overlooked because application examples use convenient names such as host and port. Real configuration may use route patterns, country codes, version labels, or identifiers imported from another system. Those names must survive as the strings the consumer expects, including names that appear unusual in plain YAML.
import assert from 'node:assert/strict';
import * as yaml from 'js-yaml';
import { jsonToYaml } from './lib/json-tools/convert.mjs';
const source = { 'true': 'flag-label', '001': 'identifier',
'a:b': 'route', '': 'empty-key' };
const result = yaml.load(jsonToYaml(source), { schema: yaml.JSON_SCHEMA });
assert.deepEqual(Object.keys(result).sort(), Object.keys(source).sort());
assert.deepEqual(result, source);
Do not fix a failing test by silently renaming source keys. If the application forbids an empty key or punctuation, reject that configuration through its schema or an explicit normalization rule. Data preservation and application validity are separate questions; a successful preservation test can correctly accompany an application rejection.
The YAML specification distinguishes the representation model from presentation syntax. In practical terms, quoting a key differently need not change its identity, while interpreting it as a different scalar type can. Compare the parsed mapping, not only whether the emitted line contains familiar text.
Keep the fixture small enough that a reviewer can identify each association. An enormous snapshot may show a changed quote without revealing whether the key's meaning changed. Pair an exact key-set assertion with a representative value assertion, and add newly introduced unusual keys when the configuration schema evolves.
3. Do multiline values preserve every required line break?
Compare multiline strings exactly after parsing, including zero, one, and several trailing newlines. YAML block style and chomping indicators affect the resulting value, not merely its appearance. Preserve fixtures for scripts, templates, and whitespace-sensitive content, and rerun them whenever serializer settings or manual formatting changes alter emitted block scalars.
Two visually similar blocks can differ at the end of the string. That matters when the value is passed to another program, compared byte-for-byte, or used to generate an artifact. Avoid trimming the expected string just to make a test pass: trimming removes the boundary you are trying to verify.
import assert from 'node:assert/strict';
import * as yaml from 'js-yaml';
import { jsonToYaml } from './lib/json-tools/convert.mjs';
for (const text of ['first\nsecond', 'first\nsecond\n',
'first\nsecond\n\n', ' indented\nlast', 'café\nગુજરાતી']) {
const emitted = jsonToYaml({ text });
assert.equal(yaml.load(emitted, { schema: yaml.JSON_SCHEMA }).text, text);
}
In the tested version, different trailing-newline counts produce different chomping styles, and a keep-chomped value can include an explicit document-end marker. Do not replace these forms with one favorite visual style without checking the resulting string. The JS-YAML changelog records August 2026 changes for trailing-newline preservation and an October 6 block-scalar loading fix.
Those maintenance notes support regression testing, not a claim that all current multiline output is defective. Version-specific fixtures establish what the installed implementation does. Keep them when upgrading rather than assuming a newer version makes exact-value checks unnecessary.
Also test the consumer's interpretation of the string after YAML parsing. A shell or template engine can apply another layer of expansion. The local example does not execute scripts or prove command safety. It only checks that the configuration layer supplies the intended string before another interpreter handles it.
4. Do empty and nested collections retain their shape?
Verify empty arrays, empty objects, nested lists, nulls, and single-item collections after parsing the emitted YAML. Preserve the distinction between absent fields and explicit empty values. Do not flatten or remove containers for readability unless the receiving application's contract explicitly permits that transformation and tests the resulting behavior.
An empty list can mean disable all entries, while an absent setting can mean inherit defaults. An empty object can mean a deliberately supplied configuration section. Conversion must not decide those meanings by dropping values that happen to look uninteresting in an editor.
import assert from 'node:assert/strict';
import * as yaml from 'js-yaml';
import { jsonToYaml } from './lib/json-tools/convert.mjs';
const source = { emptyList: [], emptyObject: {}, note: null,
blank: '', one: [{ id: 'A' }], matrix: [['A'], ['B', 'C']] };
const result = yaml.load(jsonToYaml(source), { schema: yaml.JSON_SCHEMA });
assert.deepEqual(result, source);
assert(Array.isArray(result.one));
assert.equal(result.one.length, 1);
assert.equal(Object.hasOwn(result, 'missing'), false);
The JSON null versus missing guide explains the source-side distinction. Preserve it through the serializer first, then apply any documented configuration defaults in the application layer. Otherwise, debugging becomes difficult because conversion and defaulting have changed the same field at different times.
Inspect deeply nested collections with fixtures shaped like the real configuration. A small matrix is useful for detecting accidental flattening, but it does not prove every application accepts nested sequences. Add a negative application test when a container type is forbidden, even if both JSON and YAML can represent it.
Keep null and empty-string cases separate in assertions. Comparing only truthiness would treat them alike and miss a regression. When a receiver fills in defaults, capture both the parsed configuration and the final effective configuration so the point of change is visible.
5. Is generated output controlled without inventing lost metadata?
Pin serializer options and compare generated text when reproducible output matters, while separately checking parsed data equality. Decide whether aliases, key sorting, and line wrapping are permitted. Do not promise that JSON-to-YAML conversion can reconstruct comments, original anchors, or formatting that ordinary JSON data never contained in the first place.
The repository helper sets indentation, disables line wrapping, and disables reference aliases. Review those settings in the conversion source. They make generated output easier to inspect, but they are not a portability certificate for every YAML consumer or a substitute for testing values.
import assert from 'node:assert/strict';
import * as yaml from 'js-yaml';
import { jsonToYaml } from './lib/json-tools/convert.mjs';
const shared = { endpoint: 'https://example.invalid/' + 'a'.repeat(120) };
const source = { first: shared, second: shared };
const emitted = jsonToYaml(source, { indent: 4 });
assert.equal(emitted, jsonToYaml(source, { indent: 4 }));
assert(!emitted.includes('&ref_'));
assert(!emitted.includes('*ref_'));
assert.deepEqual(yaml.load(emitted, { schema: yaml.JSON_SCHEMA }), source);
Repeated object identity in this programmatic fixture is not something ordinary JSON encodes; the example exercises the helper's no-reference option deliberately. Its exact generated text is deterministic for this input and configuration. It is not canonical YAML, and the assertions do not establish stable bytes across arbitrary library upgrades.
If comments or handwritten layout must survive, keep the original YAML or choose a document-aware editing approach. The yaml library documents comment and document handling. That is a different workflow from serializing a JSON value and should be tested as such.
Store an approved output snapshot when review tools depend on clean diffs. On upgrades, inspect text changes alongside parsed-value changes: quote style alone can be harmless, while changed scalar values require a contract decision. Avoid normalizing away the very difference that explains a deployment failure.
6. Does the application accept the exact emitted configuration?
Run the emitted YAML through the production loader and the application's validation or dry-run workflow before deployment. A serializer can preserve every source value while faithfully producing invalid application configuration. Test required fields, allowed types, ranges, and references, and distinguish syntax acceptance from permission to apply operational changes.
Start with the minimal accepted configuration and add a deliberately invalid case. A validator that only ever sees valid fixtures can be disconnected or misconfigured without anyone noticing. Require the negative fixture to fail through the expected validation channel, not because a file was missing or a command could not start.
import assert from 'node:assert/strict';
import * as yaml from 'js-yaml';
import { jsonToYaml } from './lib/json-tools/convert.mjs';
const validateExample = config => {
assert.equal(typeof config.service, 'string');
assert(config.service.length > 0);
assert(Number.isInteger(config.replicas));
assert(config.replicas >= 0 && config.replicas <= 10);
};
const load = value => yaml.load(jsonToYaml(value), { schema: yaml.JSON_SCHEMA });
validateExample(load({ service: 'worker', replicas: 2 }));
assert.throws(() => validateExample(load({ service: 'worker', replicas: '2' })));
This is an illustrative application contract, not a Kubernetes, Compose, or workflow validator. Replace it with the receiving system's supported validation. Do not claim that this example proves deployment readiness, resolves references, checks credentials, or evaluates policy. Its purpose is to show where semantic validation belongs after successful conversion.
Use the same output file throughout the pipeline. Validating one temporary document and deploying a regenerated variant leaves room for settings or environment differences. Record the serializer version, options, fixture input, and validation result so a reviewer can reproduce the acceptance decision.
Finally, keep operational approval separate. A dry run can have platform-specific limits, and parsing does not grant authority to modify infrastructure. The JSON configuration deployment guide covers related release boundaries. Conversion is finished when the intended consumer reads the intended values; deployment still needs its own checks and permissions.