Command Palette

Search for a command to run...

JSON to Go Struct: Turn an API Response Into Ready-to-Compile Types

JSON to Go Struct: Turn an API Response Into Ready-to-Compile Types

T
Toolz Team
|Aug 31, 2026|16 최소 읽기

데이터 도구 모음의 일부

Most of my day job is TypeScript and PHP, but a good chunk of the tooling behind toolz.dev and the little services I write around it is Go, and every time I wire up a new API client I hit the same wall. You have a JSON response in front of you, and you need the Go struct that json.Unmarshal will decode it into. Writing that struct by hand is tedious in a very specific way: it is not hard, it is just fiddly, and the fiddly bits are exactly where you make mistakes. So I built a converter into the site, and this is the guide to it.

TL;DR: The Toolz JSON to Go struct converter parses a JSON document and emits the Go type declarations that describe it. Objects become inline structs, arrays of objects are merged into one struct, and every field gets a json:"key" tag so encoding/json maps the data back correctly. Field names follow Go conventions, so id becomes ID and user_url becomes UserURL. Switch integers to int64, add omitempty, and rename the root type. It runs entirely client-side: no upload, no signup, works offline.

What is a JSON to Go struct converter?

A JSON to Go struct converter turns a JSON document into the Go type declarations that describe it, so you can decode the same data with the standard library's encoding/json package instead of typing the struct by hand. Every JSON object becomes a Go struct, every field carries a json:"key" tag that maps it back to the original name, and the scalar types are inferred from the values. A JSON string becomes string, a whole number becomes int, a number with a decimal point becomes float64, true and false become bool, and null becomes interface{}.

The reason this is useful is that a JSON object and a Go struct describe the same shape, a set of named fields with typed values, but Go is statically typed and JSON is not, so the struct has to spell out every type up front. Deriving those types from a sample response is pure mechanics, and mechanics done by hand across a nested payload are where the bugs live. The converter does the derivation deterministically and hands you back a type block that compiles.

It is worth being clear about the two halves of the problem, because they are what a naive converter gets wrong. The first is naming: Go only encodes exported fields, the ones with a capital first letter, and the community lint rules want common initialisms fully capitalised. The second is the tag: the json:"..." tag has to hold the exact original key or json.Unmarshal silently skips the field at runtime with no error. Get either wrong and your decode returns a struct full of zero values while everything looks fine. The tool handles both for you.

Why not write the struct by hand?

This is the honest question, because for a flat three-field object, hand-writing the struct is faster than opening a tool. The converter earns its place on the responses that are not flat: a payload with nested objects, arrays of objects, and twenty fields where half the keys are snake_case and a few are initialisms like id, url, and api_key.

On a response like that, the hand-written version has three failure modes I hit repeatedly. You forget to export a field, so it stays lowercase and encoding/json refuses to populate it. You capitalise the field but forget the tag, so UserName looks for a JSON key called UserName and never finds user_name. Or you get the nesting wrong, declaring a field as a plain struct when the JSON actually wraps it in an array. None of these are compile errors. They are runtime surprises that show up as missing data, and you lose more time debugging one of them than you would have spent generating the whole struct.

The converter removes all three. It exports every field, it writes the tag from the real key, and it reads the actual JSON shape to decide between a struct and a slice of structs. What you paste in is what you get types for.

Here is the decision in one table.

Situation Use Why
A flat object with two or three fields Hand-write it Faster than opening anything
A nested API response with many fields JSON to Go struct converter Naming and tags are where hand-written structs break
You need the same shape as TypeScript JSON to TypeScript converter Same inference, different target language
You want a validation schema, not types JSON Schema generator Draft-07 rules rather than Go types

Why are the Go field names different from my JSON keys?

This trips people up the first time, so it is worth explaining rather than hiding. Go's encoding/json package only marshals and unmarshals exported struct fields, which means fields whose name starts with a capital letter. A JSON key like user_name cannot map to an unexported user_name field, so the field has to be renamed to something exported. The tool capitalises each word, turning user_name into UserName.

On top of that, the Go community's lint tooling, the rules that started with golint and live on in tools like staticcheck, wants common initialisms written in all caps. So id becomes ID, not Id; user_url becomes UserURL, not UserUrl; and html_body becomes HTMLBody. The converter knows the standard set of initialisms, ID, URL, API, HTTP, JSON, UUID, and the rest, and applies them, so the output passes the linters your CI probably runs.

The piece that makes the rename safe is the tag. Every field gets a json:"user_name" tag holding the original key exactly, so at runtime json.Unmarshal reads the JSON key user_name and writes it into the UserName field regardless of the rename. The Go blog's article on JSON and Go documents this tag mechanism, and it is the reason a renamed field still decodes correctly. Without the tag, the renamed struct would quietly fail to populate.

How are numbers and arrays of objects handled?

Two parts of the inference deserve a closer look because they are where converters differ in quality.

Numbers first. JSON has a single number type, but Go does not, so the tool has to choose. A value with no fractional part becomes int, or int64 if you turn that option on, and a value with a decimal point becomes float64. The interesting case is an array that mixes them, like [9.5, 8, 7.2]. If the tool typed that as []int it would fail to decode the 9.5, so it widens the whole slice to []float64. Any array where one element forces a wider type gets the wider type for all elements, which is the only choice that decodes every value.

Arrays of objects are the other place real payloads get interesting. An API almost never gives you an array where every object has an identical set of keys; one element has a field the others omit, or a nullable field is present here and absent there. If the tool generated a struct from only the first element, it would drop the keys that appear later. Instead it merges every object in the array into one struct that includes every key any element has. And if two elements give the same key incompatible types, say a number in one and a string in another, that field falls back to interface{} rather than the tool guessing wrong or failing outright. The result is a single struct type that can decode the entire array.

How do I use the converter?

The flow is four steps. Paste a JSON object or array into the input box; if you want to see the shape of the output first, load the sample. Give the root type a name, something like User or ApiResponse, and the tool cleans it into a valid exported Go identifier so it compiles as written. Choose int or int64 for whole numbers, and turn on omitempty if you want zero-valued fields left out when the struct is later encoded back to JSON. Then click Convert and copy the struct into your .go file.

If the JSON is invalid, the tool reports the parser's own error message rather than failing silently, so a stray trailing comma or an unquoted key is quick to find. And because everything runs in your browser, you can paste a real API response, including one with private fields, without it leaving your machine. That client-side model is the same one behind the rest of the site, described in the data privacy online tools guide.

How does this fit with the other JSON tools?

The JSON to Go struct converter is one member of a family of JSON transformers on the site, and they share the same parsing core. When you want to tidy the JSON first, the JSON formatter will indent and validate it, which is worth doing before you convert so you can read the shape you are about to type. When your target language is TypeScript rather than Go, the JSON to TypeScript converter runs the same inference and emits interfaces. When you need a validation contract instead of language types, the JSON Schema generator produces a Draft-07 schema, and when the destination is a PHP codebase, the JSON to PHP array converter writes an array literal.

The thread connecting all of them is that they treat JSON as a source of structure and translate that structure into whatever your codebase speaks. If you work across several languages, the web developer toolkit roundup shows how these pieces slot together, and the coding tools guide covers the wider set of formatters and converters.

A worked example from building a Go service

Let me make this concrete. A while back I was writing a small Go service that pulled data from a third-party API and re-exposed a slimmed-down version of it. The vendor's response was a nested object: a top-level record with an owner sub-object, a tags array of strings, and a scores array that, annoyingly, mixed integers and decimals depending on the field. Hand-typing the struct meant getting the owner nesting right, tagging every snake_case key, and deciding what to do about that mixed array.

I pasted the sample response into the converter, named the root type after the resource, and got back a struct with Owner as an inline nested struct, Tags as []string, and Scores correctly widened to []float64. The id field came out as ID with a json:"id" tag, so it both passed the linter and decoded the real key. I copied it straight into the client package and moved on to the logic, which is where I actually wanted to spend my time. The struct was not the interesting part of the work, and generating it meant it stopped being any part of the work.

The reason I reach for the converter rather than a hand-written struct in these spots is not laziness, it is that the mechanical parts, exporting, tagging, and nesting, are precisely the parts I get wrong when I am thinking about the logic instead. Offloading them to a deterministic tool means the struct is correct on the first compile, and a correct decode on the first run, which on a nested payload saves a genuine debugging session. When I need the struct to also survive being re-encoded, I turn on omitempty so the zero values stay out of the output, and the tags come through as json:"key,omitempty" ready for that round trip.

What are the limits worth knowing about?

A few honest edges. The first is null. A JSON null carries no type information on its own, so a field that is null in your sample is typed as interface{}. There is nothing the tool can infer there; if you know the real type, change it in your editor after pasting, or feed the converter a sample where that field has a real value so the type can be derived. This is a property of the data, not a shortcoming of the tool.

The second is numeric precision, and it is a property of JSON parsing in general rather than this converter specifically. The tool parses your JSON with the browser's JSON parser, which represents numbers as double-precision floats, so integers beyond roughly 2 to the 53rd power lose exactness at parse time, before the tool ever sees them. The generated int64 field is wide enough to hold a large ID, but if the ID arrives as a bare JSON number that big, the parse has already rounded it. For truly large integer IDs, the robust move is to carry them as strings in the JSON, which keeps them exact and lets you type the field as string.

The third is that inference is a starting point, not gospel. The tool gives you the types the sample supports, but only you know that a field which happens to be an integer in this response is really a nullable count, or that a float64 should be a json.Number to avoid precision loss. Treat the output as a correct first draft that you refine, not a contract carved in stone. That is true of every JSON-to-types generator, in any language.

Finally, the converter produces inline nested structs, which keeps the whole shape in one declaration you can paste as a block. That is the most common and most readable style for a generated struct, and it is what tools in the Go ecosystem popularised. If you would rather have separate named types for deeply reused sub-objects, that is a refactor you do by hand once the inline version is in your editor, and it is usually a five-second extract with your IDE.

Frequently asked questions

What is a JSON to Go struct converter? A JSON to Go struct converter transforms a JSON document into the Go type declarations that describe it. Each object becomes a struct, each field gets a json:"key" tag, and the value types are inferred, so you can unmarshal the same data with encoding/json without writing the struct by hand.

How do I convert JSON to a Go struct? Paste your JSON into the input box, enter a name for the root type, choose your integer and tag options, then click Convert. The tool prints Go struct definitions with json tags that you can copy straight into a .go file.

Why are the Go field names different from my JSON keys? Go only encodes exported fields, so each field name is capitalised, and common initialisms are made uppercase to match golint. The json:"..." tag on every field keeps the original key, so json.Unmarshal still maps the data correctly despite the renamed field.

How are JSON numbers mapped to Go types? A whole number becomes int, or int64 if you enable that option, and a number with a decimal point becomes float64. When an array mixes integers and decimals for the same position, the type is widened to float64 so every value fits.

How are arrays of objects handled? An array of objects is merged into a single struct that includes every key any element has. If two elements give the same key incompatible types, that field becomes interface{} so the struct still covers the whole array.

What happens to null values? A JSON null gives no type information on its own, so the field is typed as interface{}. If you know the real type, change it in your editor after pasting, or provide a non-null example value in the JSON so the type can be inferred.

Is my JSON uploaded anywhere? No. The conversion happens entirely in your browser using JavaScript on your device. Your JSON is never uploaded, logged, or stored, and the tool keeps working with the network disconnected once the page has loaded.

Is the JSON to Go struct converter free? Yes. It is completely free with no signup, no watermark, and no usage cap. Convert as many JSON documents to Go structs as you need for personal or commercial projects.

Comments

0 comments

0/2000 characters

No comments yet. Be the first to share your thoughts!