JSON to Go Struct Converter

Paste JSON to generate Go struct definitions with json tags. 100% client-side.

Last reviewed: April 2026

New to this tool? Click here for instructions

JSON Input
Go Struct Output
Paste JSON above to generate Go structs.

Paste any JSON document and instantly generate idiomatic Go struct definitions with proper json: tags, dependency-ordered output, and configurable omitempty handling — entirely in your browser, no upload, no signup.

What This Tool Does

This converter accepts a JSON document — a top-level object, an array of objects, or a deeply nested structure — and emits a complete set of Go type … struct definitions ready to paste into a .go file. Every field carries a json:"…" struct tag matching the original JSON key, every nested object becomes its own named type, and the output is sorted in dependency order so the file compiles in a single pass without forward declarations or reordering.

The type inference walks each JSON value and picks the narrowest idiomatic Go counterpart: strings become string, integers become int64 (deterministic across 32-bit and 64-bit build targets), floats become float64, booleans become bool, arrays of a single uniform type become typed slices like []string or []int64, arrays of objects produce a named element type plus a slice of that type, and nested objects become named sibling structs. Null values map to interface{} by default — the safest signal that the field's true type cannot be inferred from the sample — and the Omitempty mode appends ,omitempty to every tag so zero-valued fields are skipped on marshal.

Three output modes cover the most common project conventions: Struct emits multi-line definitions with one field per line and tab indentation (matches gofmt exactly), Inline Tags emits a compact single-line-per-struct variant useful for quick reference or commented examples, and Omitempty appends the ,omitempty option to every tag. Everything runs locally in your browser — no JSON payload, no struct output, and no metadata is ever transmitted to a server. The page loads no analytics about your inputs.

How to Use It: Paste, Inspect, Copy

Step 1 — Paste your JSON sample

Click into the left pane and paste a representative JSON payload. The converter parses on every keystroke (debounced 150 ms) so you can iterate quickly when you are sketching out a new payload shape. Use the Try Example button to pre-load a small profile object that exercises strings, numbers, nested objects, arrays, booleans, and nulls in a single sample.

Step 2 — Pick an output mode

The three mode chips above the panes drive output formatting. Struct is the default and produces multi-line definitions identical to gofmt output. Inline Tags packs each struct onto one line, which is useful when you need to embed a struct shape in a code comment or documentation snippet. Omitempty appends ,omitempty to every json tag — appropriate for outbound request payloads where you want to drop zero-valued fields, and acceptable for many response types as well.

Step 3 — Read the dependency-ordered output

Structs are emitted in the order Go's compiler needs them: every nested type appears before the struct that references it. You can paste the entire output block into a fresh .go file and run go build immediately. The root type is named RootObject by default; you will typically rename it to something domain-specific like User, Order, or WebhookPayload before checking the file in.

Step 4 — Copy or download

The Copy button puts the generated Go source on your clipboard. The Download button saves it to a file named output.go for direct import into your project. Run gofmt -w after pasting and the result is indistinguishable from hand-written code.

Worked Example: A User Profile Payload

The example below uses a compact payload typical of a REST API GET /users/42 response. It exercises every type the converter handles: integer, string, array of strings, explicit null, and a nested object.

Input JSON

{
  "id": 42,
  "email": "a@b.com",
  "tags": ["admin"],
  "deleted_at": null,
  "profile": {
    "name": "X",
    "age": 30
  }
}

Generated Go Output (Omitempty mode)

type Profile struct {
    Name string `json:"name,omitempty"`
    Age  int64  `json:"age,omitempty"`
}

type RootObject struct {
    ID        int64       `json:"id,omitempty"`
    Email     string      `json:"email,omitempty"`
    Tags      []string    `json:"tags,omitempty"`
    DeletedAt interface{} `json:"deleted_at,omitempty"`
    Profile   Profile     `json:"profile,omitempty"`
}

Rename RootObject to User after pasting, and consider changing DeletedAt from interface{} to *time.Time by hand — the converter has no way to know the field is a soft-delete timestamp from the JSON null sample alone. Adding the pointer makes the absence-vs-zero distinction explicit and lets you scan the type for nullable semantics at a glance. The final hand-tuned form is:

type User struct {
    ID        int64      `json:"id"`
    Email     string     `json:"email"`
    Tags      []string   `json:"tags"`
    DeletedAt *time.Time `json:"deleted_at,omitempty"`
    Profile   Profile    `json:"profile"`
}

Visualizing the Type Mapping

The diagram below shows how each JSON value type in the sample maps to its Go counterpart. Arrows indicate the direction of inference; the right column shows the struct tag that ships with each field. This is the exact mapping the converter applies — useful as a quick reference card when you are working through a payload by hand.

JSON to Go Type Mapping Table Three-column table showing how JSON value types map to Go types with corresponding json struct tags. Columns are JSON Type, Go Type, and Struct Tag. JSON Value Inferred Go Type Generated Struct Tag "id": 42 int64 `json:"id"` "email": "a@b.com" string `json:"email"` "active": true bool `json:"active"` "score": 9.5 float64 `json:"score"` "tags": ["admin"] []string `json:"tags"` "profile": { ... } Profile (struct) `json:"profile"` "items": [ {...} ] []ItemsItem `json:"items"` "deleted_at": null interface{} `json:"deleted_at,omitempty"` "mixed": [1, "x", true] []interface{} `json:"mixed"` Green = direct primitive map · Blue = generated named type · Red = lossy fallback (review by hand)
JSON-to-Go type mapping the converter applies. Red rows are inference fallbacks where the JSON sample does not contain enough information — review and refine by hand before shipping.

Common Use Cases

REST API Client Scaffolding

The fastest way to onboard a new third-party API is to call one endpoint with curl, paste the response into the converter, and drop the generated struct into a types.go file. From there you build a typed client method like func (c *Client) GetUser(id int64) (*User, error) in a few minutes. The generated struct gives you compile-time safety on field names — typos surface at go build instead of as silent zero values at runtime. For OpenAPI-described APIs you might prefer code generation from the spec, but for the long tail of services that ship JSON examples in their docs and nothing more, paste-and-convert is the shortest path.

gRPC-to-JSON Gateway DTOs

Services that expose both gRPC and HTTP+JSON surfaces — typically via grpc-gateway — sometimes need a separate JSON-shaped DTO layer when the protobuf-generated messages do not produce the desired JSON wire format. The converter is a fast way to draft those DTOs from a sample JSON payload that matches the desired wire format, then write small adapter functions that copy fields between the protobuf message and the DTO. This is preferable to overriding protobuf's JSON marshaler when the differences are structural (renamed fields, flattened nesting, additional computed fields) rather than just casing.

Configuration File Parsing

Application configuration is often stored as JSON, YAML, or TOML — all three of which decode into the same Go struct shape via the appropriate marshaler. Pasting a sample config.json into the converter produces the struct definition that drives validation and access. Combine it with the encoding/json stdlib for JSON, gopkg.in/yaml.v3 for YAML, and github.com/BurntSushi/toml for TOML — all three respect the same struct tags with their own tag key (yaml:, toml:), so you can stack tags to support multiple formats from one struct.

Log Ingestion Pipelines

When you are wiring up a log shipper or a SIEM ingestion path, the source application's JSON log lines define the upstream schema. Paste a representative log line into the converter, generate the struct, and you have a type-safe receiver for stream parsing. Combine the result with json.Decoder in streaming mode (one struct per line) and you can process gigabytes of newline-delimited JSON without holding the full file in memory. The structured representation also enables downstream code to filter, aggregate, or re-emit records as typed events into Kafka, NATS, or any other broker.

ETL Pipelines and Data Transformation

ETL jobs often start with messy JSON exported from upstream systems and end with normalized records in a relational store. The converter is the fastest way to define the inbound struct shape so downstream transformation functions can work against named fields rather than untyped map[string]interface{} values. The performance and correctness wins are significant: every map[string]interface{} field access is a runtime type assertion that can panic; every named struct field access is a compile-time check. For pipelines processing millions of records, the difference between assertion-heavy and struct-typed code can be a 5x to 10x throughput improvement plus the elimination of an entire class of crash-on-malformed-input bugs.

Edge Cases and Limitations of Go's encoding/json

A handful of encoding/json idiosyncrasies trip up new Go developers regularly. Knowing them in advance saves debugging time later.

Struct tag syntax is strict. The tag must be a raw string literal (backticks), the key must be exactly json, and the value must be a double-quoted string. The first comma-separated piece is the JSON field name; subsequent pieces are options like omitempty and string. The tag json:"-" (just a hyphen) tells the marshaler to skip the field entirely on both marshal and unmarshal — the dual of including it but omitting the name. Use json:"name,omitempty" not json:"name omitempty"; the space form silently parses as a field literally named name omitempty and produces broken JSON output.

omitempty does not skip zero-valued structs. A struct field whose value is the type's zero (every inner field at its own zero value) still serializes. To skip an entire substruct when it is empty, use a pointer to the struct so the outer field itself can be nil. Go 1.24 added the omitzero tag option that extends the semantics to cover zero-valued structs and time.Time — useful for cleaning up timestamp fields that default to the Unix epoch zero.

JSON numbers larger than 2^53 lose precision in transit. JavaScript Number values are IEEE 754 doubles with 53 bits of integer precision. JSON numbers that exceed this range — Twitter snowflake IDs, Unix nanosecond timestamps, database BIGINT columns — get rounded when they pass through any JavaScript intermediary. To preserve them end-to-end, use json.Number (which stores the raw decimal string) instead of int64, and call .Int64() only at the point of use. Enable this mode by setting decoder.UseNumber() on the json.Decoder before calling Decode.

Pointer types are how Go represents nullable fields. A field declared as *string decodes to nil when the JSON key is missing or explicitly null, and to a non-nil pointer when the key is present with any string value including the empty string. A non-pointer string field decodes both missing and null cases to the same empty string, which is information loss. Combine pointer types with omitempty to round-trip JSON without inserting spurious null fields on marshal.

map[string]interface{} is the escape hatch for unknown shapes. When the JSON shape is fully dynamic — webhook payloads from systems you do not control, or pass-through data — declare the field as map[string]interface{} or json.RawMessage. The map preserves structure but requires type assertions to read values; the raw message preserves the original bytes for later parsing or forwarding. json.RawMessage is preferable when you plan to re-marshal the data later — round-tripping a parsed map can reorder keys or change number formatting.

RFC 3339 dates need explicit handling. The encoding/json package decodes JSON strings into string by default; getting a time.Time requires the field type to be time.Time (which has built-in RFC 3339 marshaling) or a custom UnmarshalJSON for non-standard formats like Unix epoch seconds or ISO 8601 with offsets. If your upstream emits timestamps as Unix epoch integers, define a wrapper type with custom Marshal/Unmarshal methods that bridges int64 seconds and time.Time.

JSON booleans have no nullable form without a pointer. Unlike numbers and strings where the zero value is unambiguously distinct from any plausible payload, bool's zero value (false) is ambiguous with a present-and-false value. To distinguish absent, present-and-false, and present-and-true, use *bool and check for nil before dereferencing.

Heterogeneous arrays fall back to []interface{}. Arrays containing a mix of types — say, a JSON-RPC batch response where each element might be a result or an error object — cannot be expressed as a strongly typed slice. The converter falls back to []interface{} in these cases, which is structurally accurate but loses static type checking. For known-shape heterogeneous arrays, write a custom UnmarshalJSON on the slice's element type that peeks at the first byte and dispatches to the appropriate concrete type.

Behind the Scenes: encoding/json Internals and Faster Alternatives

Reflection-Based Marshal and Unmarshal

Go's standard encoding/json package uses runtime reflection (the reflect package) to walk struct types, discover field names and tags, and emit or consume JSON tokens. The first call against a given type triggers reflection-based type discovery; the package caches the resulting field map per type so subsequent calls amortize the discovery cost. Even amortized, reflection-based field access carries a measurable per-call overhead — roughly 200 to 500 nanoseconds per field on modern x86 hardware. For services that marshal millions of payloads per second, this can become the dominant cost in the request path.

The encoding/json/v2 Proposal

Go 1.21's experimental encoding/json/v2 proposal — tracked at github.com/golang/go/discussions/63397 — addresses long-standing usability complaints about the v1 API. Among the planned changes: better error reporting that identifies the exact JSON path that failed to parse, native support for inlined fields, configurable handling of unknown fields, and a unified options system that replaces the ad-hoc decoder methods. The v2 API is still under active design and has not landed in a stable release as of mid-2026; the v1 API remains the production default.

Codegen Alternatives: easyjson, ffjson, go-json, segmentio/encoding

Several third-party libraries skip reflection entirely by generating per-type Marshal and Unmarshal methods at build time from struct definitions. The classic options are easyjson and ffjson (the latter unmaintained but still functional); newer entrants go-json and segmentio/encoding achieve similar performance with a drop-in replacement API that does not require codegen. Benchmarks routinely show 3x to 7x speedups over the standard library on real workloads, with dramatic reductions in heap allocations because the generated or specialized code can stream directly into pre-sized buffers without intermediate reflection-driven value boxing. The trade-off for codegen tools is an extra build step and generated files that must stay in sync with type definitions; for drop-in replacements, the trade-off is taking on a dependency outside the standard library.

Why Struct Tags Instead of Runtime Configuration

Some serialization libraries in other ecosystems (Jackson in Java, for example) configure marshaling via builder APIs or runtime annotations evaluated by the serializer. Go's struct tags take a different approach: serialization metadata lives directly on the type definition, where it is visible to anyone reading the type and where it cannot drift out of sync with the struct shape. The trade-off is that tags are stringly typed — there is no compile-time check that json:"foo,omitempty" is well formed, and the vet tool ships a structtag check exactly because typos in tag syntax are otherwise undetectable until runtime. The community convention is to run go vet as part of the standard test pipeline; the structtag check catches the most common errors.

Comparison: This Tool vs. mholt's json-to-go vs. VS Code "Paste JSON as Code" vs. gjson

The Go ecosystem has several adjacent tools that overlap with this converter in different ways. The right choice depends on whether you want a one-shot conversion in the browser, an IDE integration, a deployment-time codegen step, or a runtime JSON query library that bypasses struct definitions entirely.

mholt/json-to-go (mholt.github.io/json-to-go)

json-to-go by Matt Holt is the original web-based JSON-to-Go converter and the inspiration for many similar tools, this one included. It runs entirely in the browser, supports the same core type mapping, and has been battle-tested for nearly a decade. Differences are stylistic and ergonomic: this converter offers explicit mode chips for Struct / Inline Tags / Omitempty, embeds within the broader ThisDevTool toolchain so you can chain it with JSON Formatter and JSON Schema Validator in the same session, and ships with an inline reference diagram and worked example on the same page. For pure conversion of a clean JSON sample, both tools produce equivalent output.

VS Code "Paste JSON as Code" extension (quicktype)

quicktype, available as the "Paste JSON as Code" extension in VS Code, is the most feature-rich option for IDE-resident workflows. It supports a dozen target languages, infers shared types across multiple JSON samples (so { "a": 1 } and { "a": 2, "b": "x" } merge into a single struct with an optional b), and integrates directly with the editor. The cost is that it lives inside an editor — for quick one-off conversions outside of a project, opening the IDE is overhead. Choose quicktype when you are working in VS Code and want multi-sample inference; choose this converter when you want a single-keystroke browser experience or when you are working in another editor.

tidwall/gjson — runtime query, no struct generation

gjson is a different category of tool entirely: it provides path-based read access to JSON values without requiring you to define a struct at all. Code like gjson.Get(jsonBytes, "profile.name").String() extracts the nested name field directly from the raw JSON bytes. This is the right answer when you only need to read a handful of fields from a large JSON document, when the shape varies between requests, or when you want to avoid the allocation cost of full decoding. It is the wrong answer when you need to round-trip the JSON (read, modify, re-emit) or when you want compile-time field-name checking. The companion library sjson handles the write path with the same philosophy.

Quick Picker

If you have a single clean JSON sample and want struct output in your clipboard in under five seconds: this converter or mholt's. If you are working inside VS Code and want multi-sample inference: quicktype. If you need to query a few fields from a large or variable JSON payload at runtime without defining a struct: gjson. The four tools are complementary, not competitive.

Frequently Asked Questions

Plain int is platform-dependent: 32 bits on 32-bit systems, 64 bits on 64-bit systems. For JSON deserialization where a number might exceed 2,147,483,647 (Twitter snowflake IDs, Unix nanosecond timestamps, database BIGINT columns), this creates silent overflow bugs on 32-bit builds. The converter defaults to int64 because it provides deterministic range regardless of build target, matches the JavaScript Number maximum safe integer of 2^53 minus 1 closely enough for most JSON sources, and is the type Go's json package decodes JSON numbers into when you target interface{}. Use int only when you know the field is bounded and platform-portability is not a concern.
Yes, when you need to distinguish between absent, present-but-zero, and present-with-value. A non-pointer int field cannot tell you whether the JSON contained the key with value 0 or omitted the key entirely; both decode to the same zero value. A pointer type (*int, *string, *time.Time) decodes to nil when the JSON key is missing or explicitly null, and to a non-nil pointer when the key is present with any value including the zero value. Combine pointer types with omitempty to round-trip JSON cleanly: nil pointers are omitted on marshal, missing keys decode to nil on unmarshal, and zero values you want to preserve get marshaled because they reside behind a non-nil pointer.
Standard encoding/json collapses both into the field's zero value for non-pointer types, which is information loss. To distinguish them, use one of three patterns: pointer types (nil for both null and missing, but you lose the distinction between the two); a custom UnmarshalJSON that inspects the raw bytes for the literal null token; or a wrapper type like sql.NullString from database/sql that carries an explicit Valid bool alongside the value. For tri-state semantics (present-with-value, present-and-null, missing) you need json.RawMessage or a generic Option-style type — Go 1.21 introduced generics that make this ergonomic to implement.
omitempty tells the json marshaler to skip a field whose value is its zero value: empty string, integer or float 0, false, nil pointer, nil interface, nil map, nil slice, or zero-length array. It does not skip a struct that contains only zero-valued fields — a struct literally must be its zero type to be considered empty, and Go has no concept of structural emptiness. omitempty also does not affect unmarshal behavior; it only changes marshal output. For struct fields you want skipped when all inner fields are zero, you must use a pointer to the struct so the outer field can itself be nil. Go 1.24 added the omitzero tag option which extends the semantics to cover zero-valued structs and time.Time.
Backticks delimit Go raw string literals. Inside backticks, no escape sequences are processed and the string can contain double quotes verbatim, which is exactly what struct tag syntax needs because tag values are themselves double-quoted strings. The full tag form is `key:"value" key2:"value2"` where each key-value pair is space-separated and each value is enclosed in interpreted double quotes. Reflection libraries parse this format via reflect.StructTag.Get("key"), which expects the strict quoted-pair layout. Using regular double-quoted strings would force you to escape every internal quote and would make the tag unreadable.
Yes, but the type system forces an explicit choice. The simplest approach is []interface{} (alias []any in Go 1.18 plus), which accepts any mixture of strings, numbers, booleans, nested objects, and arrays at the cost of requiring type assertions to extract concrete values. The structured alternative is a custom UnmarshalJSON method on the slice's element type that peeks at the first byte of each raw element, dispatches to the correct concrete type, and stores the result in a discriminated union or tagged struct. For known-schema heterogeneous arrays — JSON-RPC batched responses for example — define a wrapper struct with json.RawMessage fields and decode each element in a second pass once its kind is known.
Standard encoding/json uses runtime reflection on every marshal and unmarshal call, which is convenient but costs roughly 200 to 500 nanoseconds per field for type discovery. Codegen-based alternatives like easyjson, ffjson, and the more recent go-json and segmentio/encoding emit per-type Marshal and Unmarshal methods at build time, bypassing reflection entirely. Benchmarks routinely show 3x to 7x speedups on real workloads and dramatic reductions in heap allocations because the generated code can stream directly into pre-sized buffers. The trade-off is an extra build step and generated files that need to stay in sync with type definitions. For services that marshal millions of payloads per second, the win is decisive; for ordinary REST clients, encoding/json is fast enough that the operational simplicity wins.
No, and the omission is intentional. Go generics, introduced in 1.18, are valuable for container types and algorithms where the element type is a true compile-time parameter — they are not useful for JSON struct definitions where the field types are concrete and known from the JSON sample. Generating Response[T] or Envelope[T any] wrappers would force the consumer to specify a type argument at every call site without saving any keystrokes versus a concrete struct. The generated output uses plain concrete types because they decode faster (no method-set indirection), produce simpler stack traces, and integrate cleanly with reflection-based libraries that predate generics. If you need a generic wrapper, write it by hand once around the generated concrete struct.

Quick reference

JSON to Go Struct Converter Quick Reference
Parameter Description Example Notes
JSON Input Key-value pairs representing data structure {"name": "Alice", "age": 30} Must be valid JSON format
Struct Name Go struct identifier UserProfile Follows Go naming conventions
Field Type Go data type for struct field string, int, bool Supports complex types like slices
Tags Metadata for struct fields json:"name" required Optional but recommended for mapping
Required Fields Fields that must exist in JSON name, email Enforced during struct creation
Nested Structures Embedded structs within main struct Address{Street, City} Requires proper nesting syntax