← index #1719Issue #5677
Off-topic · high · value 0.286
QUERY · ISSUE

JSON module does not throw exception when object is not serialisable

openby peterhinchopened 2015-12-14updated 2025-07-15
bug

This came up in http://forum.micropython.org/viewtopic.php?f=6&t=953&p=7551#p7551
cPython throws an exception on an attempt to serialise a bytes object (regardless of object contents):

>>> a = bytes(x for x in range(256))
>>> z = json.dumps(a).encode('utf8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.4/json/__init__.py", line 230, in dumps
    return _default_encoder.encode(obj)
  File "/usr/lib/python3.4/json/encoder.py", line 192, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.4/json/encoder.py", line 250, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib/python3.4/json/encoder.py", line 173, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff' is not JSON serializable

MicroPython does not throw an exception but does not work: the restored object is of type str and its contents differs from that saved:

>>> a = bytes(x for x in range(256))
>>> z = json.dumps(a)
>>> x = json.loads(z)
>>> len(x)
192
>>> len(a)
256
>>> type(a)
<class 'bytes'>
>>> type(x)
<class 'str'>
>>> 
CANDIDATE · ISSUE

Truth value test of class deriving from native class can raise TypeError

closedby stinosopened 2020-02-20updated 2021-08-23
py-core

Don't know if this has always been like this, but it looks like a bug and otherwise it should probably be mentioned as a difference with CPython for instance. Python standard says

By default, an object is considered true unless its class defines either a
__bool__() method that returns False or a __len__() method that returns zero,
when called with the object.

But when taking a native type which doesn't implement __bool__ nor __len__ this happens:

import uctypes

class C:
  pass

class B(uctypes.struct):
  def __init__(self):
    super().__init__(1, 'i')

def TestBool(x):
  print(type(x), x, 'compares', True if x else False)

TestBool(C())
TestBool(uctypes.struct(1, 'i'))
TestBool(B())

output:

<class 'C'> <C object at 3a7f20> compares True
<class 'struct'> <struct ERROR 1> compares True
Traceback (most recent call last):
  File "D:\foo.py", line 43, in <module>
  File "D:\foo.py", line 39, in TestBool
TypeError: unsupported type for __bool__: 'struct'

So even though struct itself doesn't explicitly implement __bool__, MicroPython falls back to default truth testing. But for a derived class it does not. I cannot pinpoint the exact reason, but for the derived class the unary_op call is forwarded by instance_unary_op to mp_unary_op which raises an exception, whereas in the other cases (class C above and bare uctypes.struct), mp_obj_is_true just falls through. So maybe the logic used in instance_unary_op is not correct, or mp_obj_is_true could try to catch exceptions but that seems far fetched.

The workaround is to manually add something like

def __bool__(self):
  return not not super()

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied