Skip to content
datahacks.dev
JSON

JSON Data Types and What Is Missing From Them Entirely

JSON has six value types and no date, binary, integer, decimal, set or undefined. See what Python and JavaScript do to each missing type when they serialize.

6 min read
Summarize in AI
Flat illustration of a tree of nested objects beside the document it came from, with the JSON logo.

JSON looks like it has types because JavaScript does. It has six, and they are fewer than the ones you use. A value is an object, an array, a string, a number, true or false, or null. There is no date, no binary blob, no set, no map with non-string keys, no decimal that stays exact, no distinction between an integer and a float, no undefined and no way to write infinity. Whatever your program has beyond those six, it has to squeeze into them when it writes JSON and rebuild when it reads, and the squeeze is where data changes without an error. This article runs the missing types through Python and JavaScript, shows what each does and gives the encodings that keep the data intact. It builds on the grammar described in what JSON allows and what parsers quietly accept.

The six, and what they hold

An object maps string names to values. An array is an ordered list. A string is Unicode text. A number is a decimal literal with no stated range. true and false are the booleans, and null is the one value for nothing. That is the full list.

Two properties follow that matter later. Object keys are always strings. And a number is just digits: JSON does not say that 1 is an integer and 1.0 a float, or that a number fits in 64 bits. Reading those distinctions is up to the parser, and different parsers read them differently (how JSON handles big numbers).

Python: refuse or convert

Python's json.dumps knows the six types plus a few obvious cousins: dict, list and tuple (written as an array), str, int, float, bool and None. For anything else it raises:

Text
datetime   TypeError: Object of type datetime is not JSON serializable
set        TypeError: Object of type set is not JSON serializable
bytes      TypeError: Object of type bytes is not JSON serializable
Decimal    TypeError: Object of type Decimal is not JSON serializable
tuple      [1, 2]
int key    {"1": "a"}
tuple key  TypeError: keys must be str, int, float, bool or None, not tuple
nan        NaN

The errors are the safe behavior: the library will not guess how to write a date, so you must say. The silent conversions are the dangerous ones. A tuple becomes an array, and when it is read back it is a list, so (1, 2) does not survive a round trip. An integer key becomes a string: {1: "a"} is written {"1": "a"}, and reading it back gives {'1': 'a'}, so the key's type changed, which is a classic cause of a lookup that works in memory and fails after a cache. And float("nan") is written as the bare word NaN, which is not valid JSON; Python allows it by default, and most other parsers reject it. Turn that off with allow_nan=False, which raises ValueError: Out of range float values are not JSON compliant: nan (is NaN or Infinity valid in JSON).

To serialize a type that Python does not know, pass a default function that converts it, for example turning a datetime into an ISO 8601 string and a Decimal into a string, and raising TypeError for anything unknown so that the safe behavior remains.

JavaScript: quietly change

JSON.stringify is more permissive, and its choices are worth knowing because they lose information without a message. The same sort of values:

Text
undefined    {"b":1}                      (the key with undefined is dropped)
function     {"b":1}                      (so is a method)
array undef  [null,null]                  (in an array, undefined and functions become null)
Date         {"d":"2026-10-02T08:15:00.000Z"}
NaN          {"n":null,"i":null}          (NaN and Infinity become null)
Map          {}
Set          {}
BigInt       TypeError: Do not know how to serialize a BigInt
Symbol key   {"y":2}

A property whose value is undefined disappears: { a: undefined, b: 1 } serializes as {"b":1}, so "the field is missing" and "the field was set to undefined" are the same on the wire. The same value in an array becomes null, so an array's length is preserved and its content is not. NaN and Infinity become null, which is valid JSON and a silent change of meaning: a missing measurement and a division by zero look identical. A Map and a Set serialize as {}, an empty object, with no error, which is the worst of these, because the data is gone and the output looks fine. And a BigInt raises, which is again the safe behavior. A Date has a toJSON method and becomes an ISO 8601 string in UTC with milliseconds: 2026-10-02T08:15:00.000Z. When it is read back, it is a string, not a date, and you must convert.

Encoding the missing types

Each missing type needs an agreed encoding, and the right one depends on how the data is used. The usual choices:

TypeCommon encodingNotes
Date or timestampISO 8601 string, UTC or with an offsetNever a locale string; see JSON dates
Binarybase64 stringAdds a third in size; say the variant (standard or URL-safe)
Decimal and moneystring, or integer minor unitsFloats cannot hold 0.1 exactly
Big integer IDstringAbove 2^53 a float loses digits
Setarray, sorted if order should not matterDocument that it is a set
Map with non-string keysarray of [key, value] pairs, or an array of objectsKeys must be strings in an object
Integer vs floatthe number as written, and a schema that says which1 and 1.0 may read the same in JavaScript
Missing valueomit the field, or nullChoose one; say whether they differ
Infinity, NaNnull, or a string, or omitNot representable as numbers
EnumstringStable names, not numbers

Write the choices in the interface description, ideally with a JSON Schema, and test them in both directions: write, read back, compare the typed values, not the text. A date serialized by one service as 2026-10-02T08:15:00Z and by another as 2026-10-02T08:15:00.000+00:00 is the same instant and different text, so consumers must parse, not compare strings.

The null and missing question

JSON's null raises a design question that the type list hides. A field can be present with a value, present as null, or absent, and those are three states that many APIs collapse into two. In JavaScript, absent and undefined coincide on the wire. In databases, null means unknown. In PATCH-style updates, an absent field means "leave as is" and null means "clear it", and a client that drops null fields as an optimization breaks the second. Decide the semantics per API, write them down and test the three cases. In CSV the same question appears as an empty cell, and round tripping exports back into imports shows what happens when the distinction is lost.

When you need types JSON cannot express

If the data is full of types JSON lacks, dates, decimals, binary, sets, the encodings above work and cost you a schema and discipline. For some jobs a different format is better. Protocol Buffers, Avro and Parquet carry types in the schema; MessagePack and CBOR add binary and a few types while keeping the JSON data model; BSON adds dates, binary and 64-bit integers for MongoDB. If humans do not need to read the bytes, those formats remove the guesswork. If they do, JSON with a schema is a fair middle path.

Whichever way you go, make the encoding explicit in code, with a small serializer function in one place that every write goes through, and a matching deserializer for reads. The scattered str(value) calls are where a date gets written two ways. The Python behavior is documented in the json module reference, and the JavaScript rules in the MDN page for JSON.stringify.

Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.