JSON Array vs Object: Syntax, Access, and When to Use Each
Last updated:
JSON has two structured types — the array ([...]), an ordered list indexed by integers, and the object ({...}), an unordered collection of string-keyed name-value pairs. Either can be the top-level value of a JSON document under RFC 8259. The rule of thumb is short: use an array for ordered, homogeneous lists; use an object for a fixed set of named, heterogeneous fields. Everything else in this guide is corollary.
Have a JSON blob and not sure if it is an array or an object? Paste it into Jsonic's JSON Validator — it shows the parsed structure, types, and any syntax errors with line numbers.
Validate JSONSyntax side-by-side
The two shapes look similar at a glance — both wrap a comma-separated list in brackets — but the bracket type, the presence of keys, and the access pattern differ completely. Compare:
// Array — ordered, integer-indexed
[
"alpha",
"beta",
"gamma"
]
// Object — unordered, string-keyed
{
"name": "Ada Lovelace",
"born": 1815,
"active": true
}| Aspect | Array | Object |
|---|---|---|
| Brackets | [ ] (square) | { } (curly) |
| Element form | value | "key": value |
| Access in JS | arr[0] | obj.name or obj["name"] |
| Index/Key type | 0-based integer (implicit) | string (required) |
| Order guarantee | Spec-guaranteed | Implementation-defined |
| Length / size | arr.length | Object.keys(obj).length |
| Iterate | for (const x of arr) | for (const [k, v] of Object.entries(obj)) |
| Schema type | "array" | "object" |
Note: the inside of either shape is a JSON value, which can itself be an array, object, string, number, boolean, or null. So arrays of objects, objects of arrays, and deeply nested combinations are all valid.
When to use a JSON array
Pick an array whenever position matters or the consumer will iterate the whole collection. The classic signals:
- Ordered lists — search results ranked by relevance, a feed sorted by timestamp, a leaderboard sorted by score. The array preserves rank; an object would not.
- Time-series points — sensor readings, stock prices, log entries. Each element has the same shape and the sequence is the data.
- Table rows — when you are modeling rows in a database query result, the natural JSON is an array of row objects.
- Repeated values of the same type — tags on a post (
["js", "json", "schema"]), user IDs in a permission list, coordinates on a polygon. - Sequences in algorithms — a sequence of moves, a path of waypoints, a list of operations to apply in order.
A useful test: if you renamed every key to "item_1", "item_2", "item_3", would you lose information? If yes, you have an object; if no, you should be using an array.
When to use a JSON object
Pick an object whenever you have a fixed set of named fields, each with its own meaning. The keys describe the data; the value types can differ across keys.
- Records with named fields — a user (
{"id": 1, "email": "...", "createdAt": "..."}), a product ({"sku": "...", "price": 19.99, "inStock": true}), an event payload. - Configuration files —
package.json,tsconfig.json,.eslintrc.json. Each setting has a specific meaning and the parser looks it up by name. - API responses with metadata — wrapping the list in
{"data": [...], "nextCursor": "...", "total": 42}lets you add sibling fields without breaking clients. - Heterogeneous fields — when one field is a string, another a number, another a nested object. Arrays imply uniformity; mixing types in an array signals you should be using an object.
- Lookup tables — when consumers retrieve a single item by id and you want O(1) access:
{"alice": {...}, "bob": {...}}rather than scanning an array.
Accessing values in JavaScript, Python, and Go
Same JSON document, three host languages, three access patterns. Suppose the parsed value is data and we want both array and object access.
// JavaScript / TypeScript
const data = JSON.parse(raw);
// Array access
data.users[0]; // first element
data.users.length; // count
data.users.map(u => u.id); // transform
// Object access
data.meta.total; // dot notation
data.meta["total"]; // bracket notation (needed for dynamic keys)
Object.keys(data.meta); // list keys
Object.entries(data.meta); // [[key, value], ...]# Python
import json
data = json.loads(raw)
# Array access (list)
data["users"][0] # first element
len(data["users"]) # count
[u["id"] for u in data["users"]] # transform
# Object access (dict)
data["meta"]["total"] # bracket
data["meta"].get("total", 0) # safe default
list(data["meta"].keys()) # keys
list(data["meta"].items()) # (key, value) pairs// Go — typed
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
type Response struct {
Users []User `json:"users"`
Meta map[string]int `json:"meta"`
}
var resp Response
json.Unmarshal(raw, &resp)
resp.Users[0].Name // array element
len(resp.Users) // count
resp.Meta["total"] // object key
// Go — untyped (interface{})
var v interface{}
json.Unmarshal(raw, &v)
m := v.(map[string]interface{})
list := m["users"].([]interface{})
first := list[0].(map[string]interface{})
first["name"]The biggest difference: Go forces you to type-assert untyped JSON at every level, while JS and Python let you walk the structure directly. In TypeScript, prefer typed parsing via Zod or io-ts over as Response casts so runtime mismatches surface as errors.
JSON Schema for arrays vs objects
JSON Schema uses the type keyword to constrain a value to either shape. The other keywords differ entirely — arrays have items, objects have properties.
// Array schema — every element must match items
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" }
},
"required": ["id", "name"]
},
"minItems": 1,
"uniqueItems": true
}// Object schema — each property keyed by name
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "type": "string", "format": "email" },
"active": { "type": "boolean" }
},
"required": ["id", "name"],
"additionalProperties": false
}When a field can be either an array or a single object (a common quirk of older APIs), combine with oneOf or anyOf:
{
"oneOf": [
{ "type": "array", "items": { "$ref": "#/$defs/user" } },
{ "$ref": "#/$defs/user" }
]
}Prefer to normalize the data upstream rather than accept both shapes — branching consumers on Array.isArray() is a code smell that tends to spread.
Arrays of objects: the most common API shape
Almost every REST list endpoint returns an array of objects, optionally wrapped in a metadata envelope. The shape is so dominant that consumers reflexively reach for .map() on whatever they get back.
// Bare array (compact, but inextensible)
[
{ "id": 1, "name": "Ada" },
{ "id": 2, "name": "Linus" }
]
// Envelope (forward-compatible — sibling fields can be added)
{
"data": [
{ "id": 1, "name": "Ada" },
{ "id": 2, "name": "Linus" }
],
"nextCursor": "eyJpZCI6Mn0",
"total": 1247
}Why the envelope wins for public APIs: once a client writes users.map(u => u.name) against a bare array, the server cannot add pagination cursors, total counts, deprecation warnings, or request IDs without forcing a breaking version bump. With the envelope, those fields slot in as new keys and old clients keep working.
Common envelope variants you will see in the wild:
{"data": [...]}— JSON:API style, minimal{"items": [...], "nextPageToken": "..."}— Google APIs{"results": [...], "count": N, "next": "url", "previous": "url"}— Django REST Framework{"data": [...], "meta": {"pagination": {...}}}— JSON:API full
Common confusions and errors
Most array-vs-object bugs come from a small set of mistakes. The table below covers the ones we see daily in support questions.
| Symptom | Root cause | Fix |
|---|---|---|
TypeError: data.map is not a function | You called .map() on an object, not an array | Use Object.entries(data).map(...) or access data.items first |
data.length is undefined | Value is an object; objects do not have .length | Use Object.keys(data).length or access the inner array |
typeof arr === "object" returns true | typeof cannot distinguish arrays from objects | Use Array.isArray(arr) instead |
| Order changes between requests | Data is in an object and the runtime did not preserve insertion order | Switch the shape to an array — order is then spec-guaranteed |
Schema validation fails on "type": ["array", "object"] | JSON Schema allows array of types, but consumers cannot easily branch | Use oneOf with two explicit subschemas instead |
API sometimes returns [], sometimes {} | Some serializers emit {} for empty maps and the client expected [] | Normalize on the server; document the contract in the schema |
In Python the cross-check is isinstance(value, list) vs isinstance(value, dict). In Go, after parsing into interface{}, type-assert: v, ok := value.([]interface{}) for arrays, m, ok := value.(map[string]interface{}) for objects.
Key terms
- Array
- An ordered sequence of JSON values written between square brackets, separated by commas:
[v1, v2, v3]. Elements are accessed by 0-based integer index. Order is preserved by the JSON spec. - Object
- An unordered collection of name-value pairs written between curly braces:
{"k1": v1, "k2": v2}. Each pair has a string key and any JSON value. Keys should be unique within a single object. - Key
- The string name of a member in a JSON object. Must be a JSON string (double-quoted). Even keys that look numeric (
"1","42") are stored and compared as strings, not numbers. - Index
- The 0-based integer position of an element in a JSON array. The index is implicit — it is not written in the JSON, only used by the consumer to access elements (
arr[0],arr[1], ...). - Top-level value
- The single JSON value that constitutes the entire document. Under RFC 8259, this may be any JSON value: object, array, string, number, boolean, or
null. Older RFC 4627 restricted it to object or array only. - JSON Schema type
- The
typekeyword in a JSON Schema document. For structured values, valid type names are"array"and"object". Type names are lowercase, singular, and exact —"Array"or"arrays"are invalid.
Frequently asked questions
What is the difference between a JSON array and a JSON object?
A JSON array is an ordered list of values written between square brackets: [1, 2, 3]. You access elements by integer index starting at 0 (arr[0]). A JSON object is an unordered collection of key-value pairs written between curly braces: {"name": "Ada", "age": 36}. You access values by string key (obj["name"] or obj.name in JavaScript). Both are structured types in RFC 8259 and either can be the top-level value of a JSON document. Pick array when you have an ordered, homogeneous list (search results, time-series points, table rows). Pick object when you have a fixed set of named fields with different meanings (a user record, configuration, API metadata). The two are routinely combined as "array of objects" — the most common shape returned by HTTP APIs.
Can a JSON file start with an array?
Yes. RFC 8259 (the current JSON spec) explicitly allows any JSON value as the top-level element, including arrays, objects, strings, numbers, booleans, and null. So a file containing just [1, 2, 3] or ["a", "b"] is valid JSON. The older RFC 4627 required the root to be an object or array, but that restriction was lifted in 2014. Most APIs still wrap responses in a top-level object ({"data": [...]}) for forward compatibility — once clients depend on a bare array, you cannot add sibling metadata fields (pagination, errors, request IDs) without a breaking change. For static data files and config exports, a top-level array is perfectly idiomatic.
When should I use an array of objects vs an object of objects?
Use an array of objects when order matters or when consumers iterate the whole collection: [{"id": 1, "name": "Ada"}, {"id": 2, "name": "Linus"}]. This is the canonical shape for list endpoints, search results, and table rows. Use an object of objects (keyed by id) when consumers look items up by a known identifier and order does not matter: {"1": {"name": "Ada"}, "2": {"name": "Linus"}}. The object-of-objects shape gives O(1) lookup by id without scanning, and is common in Redux/normalized state, Firebase Realtime DB, and JSON-LD graphs. The tradeoff: object-of-objects loses iteration order across older runtimes and is awkward to map over. Many apps store data normalized as object-of-objects and emit array-of-objects over the wire.
Why does JSON.parse return undefined when I access .length on an object?
Because objects do not have a .length property — only arrays and strings do. If JSON.parse returned an object like {"users": [...]}, then result.length is undefined, but result.users.length works because users is an array. The fix is to access the array first: data.users.length, or data.results.length, depending on your API shape. To safely check before accessing: Array.isArray(data) ? data.length : Object.keys(data).length. This bug usually appears when an API changed its response shape (e.g., started wrapping results in {"data": [...]}) without the client updating. Always log and inspect the parsed value first if .length is unexpectedly undefined.
How do I check if a JSON value is an array in JavaScript?
Use Array.isArray(value). It returns true only for actual arrays and false for everything else, including plain objects, null, and array-like values. Do NOT use typeof — typeof [] returns "object", indistinguishable from typeof {}. Do NOT use value instanceof Array either, because it returns false for arrays created in a different realm (iframe, worker). Array.isArray is the only reliable cross-realm check. In Python the equivalent is isinstance(value, list) (or isinstance(value, (list, tuple))). In Go, after json.Unmarshal into an interface{}, type-assert with _, ok := value.([]interface{}). In TypeScript, after Array.isArray narrows the type, the compiler treats value as any[] inside the conditional branch.
Can a JSON object have numeric keys?
No — JSON object keys must be strings. The grammar in RFC 8259 defines a member as string : value with no numeric-key alternative. You can write {"1": "a", "2": "b"} and the keys look numeric, but they are stored and compared as the strings "1" and "2". This matters when you parse: in JavaScript JSON.parse('{"1": "a"}') gives {"1": "a"} where "1" is a string property name; in Python json.loads it becomes {"1": "a"} with a str key, not 1: "a" with an int key. If you need numeric keys, either accept string keys and convert when needed, or restructure as an array of objects: [{"id": 1, "value": "a"}, {"id": 2, "value": "b"}]. Many languages (Go map[int]string, TypeScript Record<number, ...>) silently coerce, hiding this subtlety until you serialize back to JSON.
Does JSON guarantee key order in objects?
No — RFC 8259 explicitly says JSON objects are unordered collections of name-value pairs, and that "the names within an object SHOULD be unique" but does not mandate any iteration order. In practice, most modern runtimes preserve insertion order: JavaScript engines (V8, SpiderMonkey, JavaScriptCore) since ES2015 iterate string keys in insertion order, and Python 3.7+ dicts preserve insertion order at the language level. So JSON.stringify and json.dumps round-trip key order in those runtimes. But never depend on this for interop — Java HashMap, Go map, and older runtimes do not preserve order. If order matters semantically, use an array of objects ([{"key": "a", "value": 1}]) so the order is part of the JSON spec, not the runtime.
Why does my API return {"data": [...]} instead of just [...]?
Wrapping the array in an object is a forward-compatibility pattern. Once clients depend on a bare array, you cannot add sibling fields (pagination cursors, request IDs, error info, deprecation warnings) without a breaking change. With {"data": [...]} as the envelope, the API can later return {"data": [...], "nextCursor": "abc", "total": 1234, "_meta": {...}} and old clients still find their list at .data. Major API styles like JSON:API, GraphQL, and most REST conventions adopt this wrapper. The cost is one extra property access on the client (response.data.map(...) vs response.map(...)). The benefit is the API can evolve without versioning. Some APIs also wrap errors symmetrically: {"data": null, "error": {...}} — making error handling a uniform check rather than an HTTP-status-only signal.
Further reading and primary sources
- RFC 8259 — The JSON Data Interchange Format — Current IETF spec defining arrays, objects, and top-level value rules
- ECMA-404 — The JSON Data Interchange Syntax — ECMA standard for JSON syntax, including the grammar for arrays and objects
- MDN — Working with JSON — JavaScript-focused guide to parsing, accessing, and manipulating JSON arrays and objects
- JSON Schema — type reference — Authoritative reference for the type keyword and the array/object subtypes
- JSON.org — railroad diagrams — Visual railroad-diagram grammar for arrays, objects, and every JSON value type