ndarray
JSON serializable
Python
error handling
data serialization

Object of type 'ndarray' is not JSON serializable

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Object of type 'ndarray' is not JSON serializable appears when Python's json module receives a NumPy array or NumPy scalar instead of plain Python data types. This is common in APIs, ML inference services, logging code, and test fixtures. The fix is usually simple, but the right solution depends on whether you need quick compatibility, reusable encoding, or a better format altogether.

Why json.dumps Fails on NumPy Objects

The standard library JSON encoder only knows how to serialize built-in Python types such as dictionaries, lists, strings, integers, floats, booleans, and None. A NumPy ndarray is its own type, so the encoder raises TypeError.

python
1import json
2import numpy as np
3
4payload = {"values": np.array([1, 2, 3])}
5
6try:
7    print(json.dumps(payload))
8except TypeError as exc:
9    print(exc)

You will see the familiar error because json.dumps does not automatically turn the array into a JSON-compatible structure.

Convert Arrays With tolist()

For many applications, the simplest fix is converting the array to a Python list before encoding.

python
1import json
2import numpy as np
3
4values = np.array([[1, 2], [3, 4]])
5payload = {"values": values.tolist()}
6
7print(json.dumps(payload))

tolist() recursively converts the array into nested Python lists, which JSON can handle without special configuration.

This is usually enough for:

  • API responses with moderate-size arrays
  • configuration snapshots
  • debugging output
  • test fixtures

NumPy Scalars Can Fail Too

Arrays are not the only problem. NumPy scalar types such as np.int64, np.float32, and np.bool_ are also not standard Python primitives.

python
1import json
2import numpy as np
3
4payload = {
5    "count": np.int64(5),
6    "score": np.float32(0.75),
7    "ok": np.bool_(True),
8}
9
10try:
11    print(json.dumps(payload))
12except TypeError as exc:
13    print(exc)

Convert them explicitly when needed:

python
1payload = {
2    "count": int(np.int64(5)),
3    "score": float(np.float32(0.75)),
4    "ok": bool(np.bool_(True)),
5}
6
7print(json.dumps(payload))

Use a Custom Encoder for Reuse

If NumPy values appear in several places, a custom encoder keeps the conversion logic in one place.

python
1import json
2import numpy as np
3
4class NumpyEncoder(json.JSONEncoder):
5    def default(self, obj):
6        if isinstance(obj, np.ndarray):
7            return obj.tolist()
8        if isinstance(obj, np.integer):
9            return int(obj)
10        if isinstance(obj, np.floating):
11            return float(obj)
12        if isinstance(obj, np.bool_):
13            return bool(obj)
14        return super().default(obj)
15
16
17payload = {
18    "vector": np.array([0.2, 0.8], dtype=np.float32),
19    "count": np.int64(2),
20}
21
22print(json.dumps(payload, cls=NumpyEncoder))

This pattern is especially useful in web services, where you want one consistent rule for every response.

Think About Data Size and Precision

JSON is text, so it is easy to inspect and interoperable across languages. It is not an efficient format for large numeric arrays. A large tensor turned into JSON becomes verbose, slower to encode, slower to transmit, and slower to parse.

If the consumer is another Python process or a data pipeline, formats such as NumPy .npy, Parquet, Arrow, or a binary protocol may be a better fit. Use JSON when interoperability matters more than compactness.

Precision also matters. Converting a float32 to a Python float is usually acceptable, but you should still know that the wire format is text and may not preserve the exact memory layout you started with.

Handle Special Floating Values Explicitly

Machine learning outputs sometimes include NaN or infinity. Even if your encoder accepts them, downstream consumers may not.

python
1import json
2import numpy as np
3
4values = np.array([1.0, np.nan, np.inf])
5clean = np.nan_to_num(values, nan=0.0, posinf=1e9, neginf=-1e9)
6
7print(json.dumps({"values": clean.tolist()}))

The correct policy depends on your API contract. You might replace them, reject the payload, or map them to null. The important part is to choose intentionally instead of discovering the issue in production.

Common Pitfalls

The most common mistake is converting the top-level array and forgetting nested NumPy scalars elsewhere in the payload. That leads to intermittent failures that are harder to debug than the original one-line example.

Another issue is using JSON for large numerical outputs just because it is convenient. The code works, but performance and payload size become poor quickly.

Developers also forget to define a policy for NaN and infinity. Those values are easy to produce in numerical code and awkward to exchange across systems.

Finally, avoid scattering ad hoc conversions across the codebase. A shared encoder or boundary-layer conversion step is much easier to maintain.

Summary

  • Python's standard JSON encoder does not serialize NumPy arrays or NumPy scalar types automatically.
  • Use tolist() for arrays and convert scalars with int, float, or bool.
  • A custom JSONEncoder is the cleanest approach when NumPy values appear often.
  • Large arrays are usually a poor fit for JSON from a performance perspective.
  • Decide how your application should handle NaN and infinity before they reach clients.

Course illustration
Course illustration

All Rights Reserved.