Blog
Wild & Free Tools

How to Flatten Nested JSON in Java, C#, Go, Rust, and Kotlin — Code Patterns + Browser Shortcut

Last updated: March 2026 7 min read
Quick Answer

Table of Contents

  1. Java
  2. C#
  3. Go
  4. Rust
  5. Kotlin & Scala
  6. Browser shortcut
  7. Frequently Asked Questions

Flattening nested JSON in Python takes one line (pd.json_normalize). In Java, C#, Go, Rust, or Kotlin, it is 15-30 lines of recursive traversal. None of these languages have a built-in JSON flatten helper. You either write one yourself, pull in a library, or — for one-time work — skip the code entirely and use our browser-based JSON Flattener.

This post collects the idiomatic flatten patterns for each language, plus honest guidance on when browser beats code.

Java — Jackson Recursive Helper

Jackson is the standard JSON library in Java. To flatten nested JSON into dot-notation keys:

import com.fasterxml.jackson.databind.*;
import java.util.*;

public static Map<String, Object> flatten(JsonNode node, String prefix) {
    Map<String, Object> result = new LinkedHashMap<>();
    node.fields().forEachRemaining(entry -> {
        String key = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey();
        JsonNode value = entry.getValue();
        if (value.isObject()) {
            result.putAll(flatten(value, key));
        } else {
            result.put(key, value.isValueNode() ? value.asText() : value);
        }
    });
    return result;
}

Call it with flatten(mapper.readTree(jsonString), ""). Arrays are preserved as leaf values (JsonNode), not exploded.

For Gson (older projects), the pattern is similar but uses JsonObject and JsonElement types. No functional difference for the flatten operation.

C# — Newtonsoft.Json (JObject) Walker

Newtonsoft.Json (Json.NET) remains the most common JSON library in .NET. The idiomatic flatten:

using Newtonsoft.Json.Linq;
using System.Collections.Generic;

public static Dictionary<string, object> Flatten(JToken token, string prefix = "") {
    var result = new Dictionary<string, object>();
    if (token is JObject obj) {
        foreach (var prop in obj.Properties()) {
            string path = string.IsNullOrEmpty(prefix) ? prop.Name : prefix + "." + prop.Name;
            if (prop.Value is JObject) {
                foreach (var kv in Flatten(prop.Value, path)) result[kv.Key] = kv.Value;
            } else {
                result[path] = (prop.Value as JValue)?.Value ?? prop.Value;
            }
        }
    }
    return result;
}

For System.Text.Json (modern .NET), the pattern is similar but uses JsonElement. System.Text.Json is faster and no-allocation-heavy; Newtonsoft is more ergonomic. Most teams use Newtonsoft unless perf is a bottleneck.

Go — map[string]interface{} Traversal

Go has no JSON object type; you unmarshal into a generic map[string]interface{} and walk it:

package main

import (
    "encoding/json"
    "fmt"
)

func flatten(prefix string, src map[string]interface{}, dst map[string]interface{}) {
    for k, v := range src {
        key := k
        if prefix != "" {
            key = prefix + "." + k
        }
        if nested, ok := v.(map[string]interface{}); ok {
            flatten(key, nested, dst)
        } else {
            dst[key] = v
        }
    }
}

func main() {
    var src map[string]interface{}
    json.Unmarshal([]byte(raw), &src)
    dst := map[string]interface{}{}
    flatten("", src, dst)
    out, _ := json.MarshalIndent(dst, "", "  ")
    fmt.Println(string(out))
}

Arrays become []interface{} at leaf positions. If you need struct-based flattening (known types at compile time), reflection is required — more code, more edge cases.

Sell Custom Apparel — We Handle Printing & Free Shipping

Rust — serde_json Recursion

Rust's serde_json uses a Value enum. Flatten walks the tree and builds a new Map:

use serde_json::{Value, Map};

fn flatten(prefix: &str, src: &Value, dst: &mut Map<String, Value>) {
    if let Value::Object(obj) = src {
        for (k, v) in obj {
            let key = if prefix.is_empty() { k.clone() } else { format!("{}.{}", prefix, k) };
            if v.is_object() {
                flatten(&key, v, dst);
            } else {
                dst.insert(key, v.clone());
            }
        }
    }
}

fn main() {
    let src: Value = serde_json::from_str(raw).unwrap();
    let mut dst = Map::new();
    flatten("", &src, &mut dst);
    println!("{}", serde_json::to_string_pretty(&Value::Object(dst)).unwrap());
}

Rust's type system makes this a bit more ceremonial, but the logic is identical to the other languages. Arrays preserved as Value::Array at leaves.

Kotlin and Scala — JVM-Based Variants

Kotlin: Use Jackson (with kotlin-jackson-module) — same pattern as Java but with Kotlin idioms:

fun flatten(node: JsonNode, prefix: String = ""): Map<String, Any?> {
    val result = mutableMapOf<String, Any?>()
    node.fields().forEach { (k, v) ->
        val key = if (prefix.isEmpty()) k else "$prefix.$k"
        if (v.isObject) result.putAll(flatten(v, key))
        else result[key] = if (v.isValueNode) v.asText() else v
    }
    return result
}

Scala: play-json, circe, and json4s all have similar patterns. No library has a built-in flatten flag; you write the recursive traversal.

The JVM world in general does not provide flatten out of the box. This is part of why the browser tool gets used even by teams that write in JVM languages — it is faster than remembering which library your service uses.

The Browser Tool for Statically-Typed Developers

Statically-typed languages reward you for defining data shapes up front. Flattening breaks that — the flat output loses its type structure. You end up with Map<String, Object>, Dictionary<string, object>, or map[string]interface{}, none of which are type-safe.

For one-off flattens that do not need to integrate with your typed codebase (debugging, sample prep, ad-hoc analysis), the browser tool skips the untyped-map detour entirely. Paste, click, copy — you get flat JSON text, which you can feed into a CSV, a spreadsheet, or a colleague's inbox.

For in-service flattening where the flat output is part of your data pipeline, pick one of the patterns above and build it properly. Common places it matters: ETL services, log normalization, legacy system adapters.

The rule: type the flatten when the output needs to integrate. Skip to the browser when the output is for human consumption.

Flatten JSON Without Writing a Helper

Free browser tool — paste, click, copy. No library, no reflection.

Open Free JSON Flattener

Frequently Asked Questions

Does Jackson have a built-in flatten method?

No — Jackson provides the JsonNode API you need, but the actual flatten helper is something you write once and reuse. Some third-party libraries exist (json-flattener, github.com/wnameless/json-flattener) if you want a dependency.

Is there a native flatten in System.Text.Json?

No. System.Text.Json provides JsonElement for traversal but not flatten itself. Write a recursive helper using JsonElement.EnumerateObject(). Same pattern as Newtonsoft; different type names.

How do I flatten Go structs instead of maps?

Reflection. Use reflect package to walk struct fields. For well-typed flattening, some teams use code generation (stringer-style) to produce flatten methods at build time. For one-off work, unmarshal into map[string]interface{} first — simpler than reflection.

Can the browser tool handle output from my Java or C# service?

Yes — output is just JSON text. Whatever your service emits, paste it into the flattener, the result is the same as running your language-specific flatten helper. Faster for one-off verification.

David Rosenberg
David Rosenberg Technical Writer

David spent ten years as a software developer before shifting to technical writing covering developer productivity tools.

More articles by David →
Launch Your Own Clothing Brand — No Inventory, No Risk