In a json struct tag, what does the ,string option do to the field's encoded value?
answer
- the value gains quotes on the wire
- the Go type does not change
- browsers cannot hold every int64
- only four kinds of field accept it
- an unquoted value now fails to decode
basics
~20 sThe ,string option makes encoding/json write the field's value inside a JSON string, so an int64 holding 63 encodes as "63" rather than 63, and requires a quoted value when decoding. It applies only to string, integer, floating-point and boolean fields.
solid answer
~40 s`,string` says: this field is stored as JSON *inside* a JSON string. `ID int64` tagged `json:"id,string"` encodes as `{"id":"9007199254740993"}` instead of `{"id":9007199254740993}`, and the Go field stays a real `int64` — the quoting exists only on the wire. The usual reason is JavaScript, whose numbers are float64 and silently lose precision above 2^53, so large identifiers are conventionally transported as strings. The option applies only to fields of string, floating-point, integer and boolean type; on a slice, map or struct field it does nothing. It is symmetric and strict on the way in: decoding an *unquoted* number into a `,string` field is an error, so a producer that stops quoting the value breaks the consumer loudly rather than silently. On a string field it double-encodes, producing a quoted JSON string inside a JSON string.
code
go · 7 linestype Order struct {
ID int64 `json:"id,string"`
Amount int64 `json:"amount"`
}
// json.Marshal(Order{ID: 9007199254740993, Amount: 5})
// {"id":"9007199254740993","amount":5}go deeper
Recognise the option when you see it and know it only changes the wire form: the field stays an ordinary int64 or bool in Go, while the JSON carries quotes around the value.
Explain which four field kinds accept it, that it is ignored elsewhere, and that decoding an unquoted value is an error. Be able to give the JavaScript precision reason for using it.
Show the migration hazard: enabling it on the consumer before the producer quotes rejects every message. Decide whether to apply it per field or to define a wire type for the whole API.
Own consistency across the surface. Mixed quoted and unquoted identifiers in one API cost every client author a day, so make it a documented rule rather than a per-field judgment call.
## What the option means The `string` option in a json struct tag is documented as: the field's value is stored as JSON inside a JSON string. Concretely, the encoder produces the value it normally would, then wraps it in quotes. ```go type Order struct { ID int64 `json:"id,string"` Amount int64 `json:"amount"` } // json.Marshal(Order{ID: 9007199254740993, Amount: 5}) // {"id":"9007199254740993","amount":5} ``` Both fields are `int64` in Go. Only the wire representation differs. ## Which types it works on `,string` applies to fields of **string, floating-point, integer and boolean** type. On any other kind — a slice, a map, a struct, a pointer to one of those — it is simply ignored, with no error and no warning. Tagging `Items []int` with `json:"items,string"` does not produce `"[1,2,3]"`; it produces the ordinary array. That silence is the option's main trap: people reach for it to quote a nested object and are surprised nothing changed. On a **string** field it still applies, and the result is double-encoded: a field holding `hi` becomes `"\"hi\""` on the wire. That is occasionally what you want (a field carrying a pre-serialised JSON document) and much more often a mistake. ## Why anyone uses it The dominant reason is numeric precision at the other end of the pipe. JSON has one number type, and JavaScript implements it as an IEEE-754 double, which represents integers exactly only up to 2^53 - 1. A 64-bit database identifier such as `9007199254740993` parses in a browser as `9007199254740992` — off by one, silently, with no error anywhere in the chain. Transporting the identifier as a JSON string avoids the whole class of problem: the browser reads a string, and nothing rounds. `,string` gives you that wire shape without contorting your Go types. The field remains `int64`, so arithmetic, comparison and database round-tripping all stay natural, and only the encoding layer knows about the quotes. A second, rarer reason is a consumer that demands quoted numbers for schema reasons — some systems type every scalar as a string. ## Decoding is strict The option is symmetric, and on input it is *stricter* than the default: - input `{"id":"42"}` into a `,string` int64 field decodes to 42 - input `{"id":42}` into the same field is an **error**, reporting an invalid use of the `,string` struct tag when trying to unmarshal an unquoted value That strictness is a feature. If the producer of the payload changes its mind and starts sending a bare number, the consumer fails loudly on the first message rather than accepting an unnoticed representation change. It is also a hazard during a migration: turning `,string` on in a consumer before the producer starts quoting will reject every message. ## Where it sits in the tag `string` is an option, so it follows the name: `json:"id,string"`. Combining it with `omitempty` is legal and order-independent among options — `json:"id,string,omitempty"` and `json:"id,omitempty,string"` behave the same. Writing it as the name (`json:"string"`) does something entirely different: it renames the field to `string`. ## The alternatives, briefly If the whole API quotes every number, `,string` on each field is tedious and easy to forget on a new field; a dedicated wire type with an explicitly typed field can be clearer. If only one or two identifiers need it, the tag option is exactly the right size of tool. Either way, decide once per API rather than per field, because a payload where three of five identifiers are quoted is the version of this that confuses every client author who meets it. ## What to check when it misbehaves Three quick checks cover nearly every report of "the option is not working": is the field's kind one of the four supported ones; is `string` in the option position rather than the name position; and is the tag itself parsing at all — a stray space after `json:` disables the whole tag, options included. `go vet ./...` answers the third.
- What happens when a payload sends an unquoted number into a field tagged json:"id,string"?`json.Unmarshal` returns an error complaining about an invalid use of the `,string` struct tag on an unquoted value, and the field is not set. The option is symmetric and strict, so a producer that quietly stops quoting breaks the consumer immediately instead of half-decoding.
- Why is quoting a 64-bit identifier a common wire convention at all?JSON has a single number type, and JavaScript parses it into a float64, which represents integers exactly only up to 2^53 - 1. A larger identifier is silently rounded in the browser with no error anywhere. Sending it as a string removes the rounding step entirely.
- You tag a []int field with json:"items,string" and nothing changes. Why?The option applies only to string, integer, floating-point and boolean fields; on any other kind it is ignored silently. A slice is encoded as a normal JSON array regardless. If you truly need a quoted document there, you must produce that string yourself rather than ask the tag for it.
saying these in an interview costs you the question
- Thinks ,string changes the Go field's type
- Expects ,string to quote a slice or struct field
- Assumes an unquoted number still decodes fine
- Writes json:"string" and renames the field by accident
- Believes the option only affects encoding