TypeError: Object of type int64 is not JSON serializable means you are trying to convert a NumPy or pandas value to JSON. numpy.int64 looks and behaves like an integer, but it is a different type, and Python’s json module only knows about the built-in ones. The fix is to convert before serialising — and there are four ways to do it depending on how much data you have.
Reproducing it#
import json
import numpy as np
value = np.int64(42)
print(value + 1) # 43 - behaves like an int
print(isinstance(value, int)) # False on most platforms
json.dumps({"count": value})
TypeError: Object of type int64 is not JSON serializable
NumPy uses its own fixed-width types for speed and predictable memory layout. Python’s int is arbitrary precision and stored quite differently. The json module checks types exactly, so a lookalike is not enough.
Fix 1: convert the single value#
json.dumps({"count": int(value)}) # {"count": 42}
# or, for any NumPy scalar
json.dumps({"count": value.item()})
.item() is the general answer: it converts any NumPy scalar to its nearest Python equivalent, so it works for integers, floats and booleans alike without you having to know which you have.
Fix 2: convert a whole structure#
When the values are buried in a dictionary or list, walk it:
import numpy as np
def to_builtin(obj):
if isinstance(obj, dict):
return {key: to_builtin(value) for key, value in obj.items()}
if isinstance(obj, (list, tuple)):
return [to_builtin(item) for item in obj]
if isinstance(obj, np.ndarray):
return obj.tolist()
if isinstance(obj, np.generic): # covers every NumPy scalar type
return obj.item()
return obj
data = {"counts": np.array([1, 2, 3]), "mean": np.float64(2.0)}
print(json.dumps(to_builtin(data)))
{"counts": [1, 2, 3], "mean": 2.0}
np.generic is the base class for every NumPy scalar, so that one branch handles int64, float32, bool_ and the rest.
Fix 3: a custom encoder (the reusable option)#
import json
import numpy as np
class NumpyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, np.ndarray):
return obj.tolist()
if isinstance(obj, np.generic):
return obj.item()
return super().default(obj)
data = {"id": np.int64(7), "scores": np.array([1.5, 2.5])}
print(json.dumps(data, cls=NumpyEncoder))
{"id": 7, "scores": [1.5, 2.5]}
The default method is called only for values the encoder does not already understand, so there is no cost for ordinary data. Calling super().default(obj) at the end is important — it lets genuinely unserialisable objects still raise a clear error rather than being silently mangled.
The same thing as a function, if you prefer:
def numpy_default(obj):
if isinstance(obj, np.ndarray):
return obj.tolist()
if isinstance(obj, np.generic):
return obj.item()
raise TypeError(f"Not serialisable: {type(obj).__name__}")
json.dumps(data, default=numpy_default)
Fix 4: let pandas do it#
If the data is in a DataFrame, pandas already knows how to convert its own types:
import pandas as pd
df = pd.DataFrame({"name": ["a", "b"], "count": [1, 2]})
print(df.to_json(orient="records"))
[{"name":"a","count":1},{"name":"b","count":2}]
This is the cleanest route when your data starts as a DataFrame. Note that df.to_dict() does not convert the types — the values inside are still NumPy scalars, so json.dumps(df.to_dict()) raises the same error. Use to_json, or apply one of the fixes above afterwards.
The same error, other types#
| Message mentions | Convert with |
|---|---|
int64, int32 |
int(x) or x.item() |
float64, float32 |
float(x) or x.item() |
bool_ |
bool(x) |
ndarray |
x.tolist() |
Timestamp, datetime |
x.isoformat() |
Decimal |
float(x) or str(x) |
set |
list(x) |
A fuller encoder handling the common cases together:
import datetime, decimal
class SafeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, np.ndarray):
return obj.tolist()
if isinstance(obj, np.generic):
return obj.item()
if isinstance(obj, (datetime.datetime, datetime.date)):
return obj.isoformat()
if isinstance(obj, decimal.Decimal):
return float(obj)
if isinstance(obj, set):
return sorted(obj)
return super().default(obj)
NaN and infinity#
json.dumps({"value": float("nan")}) # '{"value": NaN}'
Python produces this without complaint, but NaN and Infinity are not valid JSON, and strict parsers — including most browsers — will reject the result. Replace them before serialising:
import math
clean = {k: (None if isinstance(v, float) and not math.isfinite(v) else v)
for k, v in data.items()}
json.dumps(clean, allow_nan=False) # now raises if any slipped through
Questions people ask#
Why does isinstance(np.int64(1), int) return False?
On most platforms np.int64 does not inherit from Python’s int. Confusingly, np.float64 does inherit from float, which is why float values sometimes serialise without complaint and integers do not.
Is there a performance cost to a custom encoder?
Negligible. The default method only runs for types the encoder cannot already handle, so ordinary strings and numbers take the fast path unchanged.
Should I use orjson instead?
orjson serialises NumPy types natively when you pass option=orjson.OPT_SERIALIZE_NUMPY, and it is considerably faster. Worth adopting if you are writing a lot of JSON; unnecessary for a one-off script.
How do I get the types back when reading?
JSON has no concept of int64, so you cannot — everything comes back as a plain Python type. Rebuild the NumPy array or DataFrame explicitly after loading, specifying the dtype you want.
Where to go next#
- Python dictionaries explained — the structure JSON maps onto.
- Solving dict TypeError — the wider family of type errors.
- Python file handling — writing the JSON out afterwards.