# How KCL Generates JSON and YAML Output from .k Configuration Files

> Learn how KCL generates JSON and YAML from .k files. Discover the evaluation and serialization process using dedicated formatter pipelines.

- Repository: [The KCL Programming Language/kcl](https://github.com/kcl-lang/kcl)
- Tags: how-to-guide
- Published: 2026-03-05

---

**KCL generates JSON and YAML by evaluating .k files into runtime ValueRef objects, then serializing them through dedicated formatter pipelines in the runtime crate that use serde_json and serde_yaml.**

When you execute a KCL program from the kcl-lang/kcl repository, the compiler transforms your configuration through several stages before emitting standard JSON or YAML documents. This pipeline involves parsing source code into an abstract syntax tree, evaluating it into runtime values, and finally invoking Rust-based serializers that respect formatting options like indentation and private field exclusion.

## The KCL Output Pipeline: From Parsing to Serialization

KCL follows a strict multi-stage architecture to transform human-readable configuration into machine-readable output.

### Parsing and Evaluation

The process begins in the compiler front-end. The **lexer** (`crates/lexer`) tokenizes the source text, and the **parser** (`crates/parser`) constructs an AST. The **resolver** (`crates/sema`) then annotates symbols with type information before the **evaluator** (`crates/evaluator`) walks the tree, creating **runtime values** represented as `ValueRef` objects.

These `ValueRef` instances hold the actual configuration data—scalars, lists, and dictionaries—that will eventually be serialized.

### Triggering JSON or YAML Generation

The runtime supports two distinct mechanisms to initiate serialization:

- **Format specifiers** in string interpolation (`#json`, `#yaml`)
- **Standard library functions** (`json.encode`, `yaml.encode`)

In [`crates/evaluator/src/node.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/node.rs), the evaluator matches format specifiers during string interpolation:

```rust
match format_spec {
    "#json" => formatted_expr_value.to_json_string(),
    "#yaml" => formatted_expr_value.to_yaml_string(),
    _ => …
}

```

Both paths ultimately invoke the same underlying formatter functions exposed via C-FFI.

## JSON Encoding Implementation

The JSON pipeline lives entirely within [`crates/runtime/src/json/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/json/mod.rs) and [`crates/runtime/src/value/val_json.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/value/val_json.rs).

### FFI Entry Points

The function `kcl_json_encode` serves as the external boundary. Defined in [`crates/runtime/src/json/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/json/mod.rs), this unsafe C-FFI wrapper accepts a context pointer, arguments, and keyword arguments:

```rust
pub unsafe extern "C-unwind" fn kcl_json_encode(
    ctx, args, kwargs,
) -> *const kcl_value_ref_t {
    // …
    let s = ValueRef::str(arg0.to_json_string_with_options(&opts).as_ref());
    s.into_raw(ctx)
}

```

The function parses options via `args_to_opts` to build a `JsonEncodeOptions` struct, then delegates to the value's stringification method.

### Value-to-JSON Conversion

The heavy lifting occurs in [`crates/runtime/src/value/val_json.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/value/val_json.rs) within `to_json_string_with_options`. This method traverses the `ValueRef` tree, converting each node into a `serde_json::Value`, then delegates to `serde_json::to_string` for the final encoding.

### Configuration Options

The `JsonEncodeOptions` struct controls output formatting:

- **`sort_keys`** – Alphabetically sort object keys
- **`ignore_private`** – Omit fields prefixed with `kcl_` (private variables)
- **`ignore_none`** – Drop null or None values from the output
- **`indent`** – Pretty-print with specified indentation spaces

## YAML Encoding Implementation

The YAML pipeline mirrors the JSON implementation but uses `serde_yaml` and supports multi-document streams.

### FFI Entry Points

In [`crates/runtime/src/yaml/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/yaml/mod.rs), the `kcl_yaml_encode` function provides the C-FFI boundary:

```rust
pub unsafe extern "C-unwind" fn kcl_yaml_encode(
    ctx, args, kwargs,
) -> *const kcl_value_ref_t {
    // …
    let s = ValueRef::str(arg0.to_yaml_string_with_options(&opts).as_ref());
    s.into_raw(ctx)
}

```

Like its JSON counterpart, it uses `args_to_opts` to construct `YamlEncodeOptions`.

### Value-to-YAML Conversion

The conversion logic resides in [`crates/runtime/src/value/val_yaml.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/value/val_yaml.rs). The `to_yaml_string_with_options` method walks the `ValueRef` tree, mapping it to `serde_yaml::Value` types before final serialization.

### Multi-Document Streams

When encoding multiple values, KCL joins documents using the constant `YAML_STREAM_SEP = "\n---\n"`, producing valid multi-document YAML streams compliant with the YAML specification.

## Practical Usage: CLI and Code Examples

You can trigger JSON and YAML generation through the command line or directly within your `.k` files.

### Command-Line Output Flags

The `kcl run` command provides shorthand flags for direct serialization:

```bash

# Output JSON to STDOUT

kcl run config.k -J json

# Output YAML to STDOUT  

kcl run config.k -Y yaml

```

### Inline Format Specifiers

Use interpolation specifiers to convert values inside your configuration:

```k
result = {"name": "demo", "count": 3}
json_str = "${result: #json}"
yaml_str = "${result: #yaml}"

```

This produces `{"name":"demo","count":3}` and `name: demo\ncount: 3` respectively.

### Standard Library Encoding

For programmatic control over formatting options, import the encoding modules:

```k
import "json"
import "yaml"

data = {"a": 1, "b": [2, 3], "kcl_internal": "secret"}

# Pretty-printed JSON with 2-space indentation

json_out = json.encode(data, indent=2)

# YAML excluding private fields (removes kcl_internal)

yaml_out = yaml.encode(data, ignore_private=true)

```

Both `json.encode` and `yaml.encode` accept options for `sort_keys`, `ignore_none`, and `ignore_private`, giving you fine-grained control over the serialized output.

## Summary

- KCL parses `.k` files into an AST, then evaluates them into `ValueRef` runtime values stored in the evaluator crate.
- **JSON generation** flows through `kcl_json_encode` in [`crates/runtime/src/json/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/json/mod.rs) and converts values via [`val_json.rs`](https://github.com/kcl-lang/kcl/blob/main/val_json.rs) using `serde_json`.
- **YAML generation** follows an identical pattern in [`crates/runtime/src/yaml/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/yaml/mod.rs) and [`val_yaml.rs`](https://github.com/kcl-lang/kcl/blob/main/val_yaml.rs), utilizing `serde_yaml` and supporting multi-document streams with `"\n---\n"` separators.
- Both formats respect **encoding options** including `ignore_private` (hiding `kcl_` prefixed fields), `ignore_none`, `sort_keys`, and indentation controls.
- You can trigger output via **CLI flags** (`-J`, `-Y`), **format specifiers** (`#json`, `#yaml`), or **stdlib functions** (`json.encode`, `yaml.encode`).

## Frequently Asked Questions

### How does KCL convert internal values to JSON format?

KCL converts internal `ValueRef` objects to JSON by calling `to_json_string_with_options` in [`crates/runtime/src/value/val_json.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/value/val_json.rs). This function traverses the value tree, maps each node to a `serde_json::Value`, and uses `serde_json::to_string` to produce the final text, respecting options like indentation and key sorting passed through the C-FFI boundary from `kcl_json_encode`.

### Can I control indentation when generating YAML output?

Yes. When calling `yaml.encode()` or using the `#yaml` format specifier, pass the `indent` option to control spacing. The YAML encoder in [`crates/runtime/src/value/val_yaml.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/value/val_yaml.rs) passes this configuration to `serde_yaml`, which handles the actual whitespace formatting in the generated document.

### What is the difference between using #json and json.encode()?

Format specifiers like `#json` are evaluated during string interpolation in the evaluator ([`crates/evaluator/src/node.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/node.rs)) and are useful for embedding JSON inside larger strings. The `json.encode()` function is a standard library call that returns a raw string value and accepts options like `indent` and `ignore_private`, making it better suited for configuring output files or API responses with specific formatting requirements.

### How does KCL handle multiple YAML documents in a single output?

When encoding a list of values or multiple top-level objects, the YAML formatter in [`crates/runtime/src/yaml/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/yaml/mod.rs) joins individual documents using the `YAML_STREAM_SEP` constant (`"\n---\n"`). This produces a valid multi-document YAML stream where each document is separated by a line containing three hyphens, allowing you to generate complex Kubernetes manifest bundles or configuration sequences from a single KCL program.