
JSON to Go Struct Converter
Paste JSON to generate Go struct definitions with json tags. 100% client-side.
Last reviewed: April 2026New to this tool? Click here for instructions
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.
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
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.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.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.`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.[]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.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.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
| 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 |