# Buggy code
def _rep(obj, expand=False):
    """Get a flat -- i.e., JSON-ish -- representation of a beets Item or
    Album object. For Albums, `expand` dictates whether tracks are
    included.
    """
    out = dict(obj)

    if isinstance(obj, beets.library.Item):
        if app.config.get("INCLUDE_PATHS", False):
            out["path"] = util.displayable_path(out["path"])
        else:
            del out["path"]

        # Get the size (in bytes) of the backing file. This is useful
        # for the Tomahawk resolver API.
        try:
            out["size"] = os.path.getsize(util.syspath(obj.path))
        except OSError:
            out["size"] = 0

        return out

    elif isinstance(obj, beets.library.Album):
        del out["artpath"]
        if expand:
            out["items"] = [_rep(item) for item in obj.items()]
        return out


# Traceback (TypeError)
~
➔ beet -vv web
* Running on http://127.0.0.1:8337/ (Press CTRL+C to quit)
127.0.0.1 - - [30/Apr/2017 11:05:15] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [30/Apr/2017 11:05:22] "GET /item/query/isabel HTTP/1.1" 200 -
Error on request:
Traceback (most recent call last):
File "/usr/lib/python3.6/site-packages/werkzeug/serving.py", line 209, in run_wsgi
execute(self.server.app)
File "/usr/lib/python3.6/site-packages/werkzeug/serving.py", line 199, in execute
for data in application_iter:
File "/usr/lib/python3.6/site-packages/werkzeug/wsgi.py", line 704, in __next__
return self._next()
File "/usr/lib/python3.6/site-packages/werkzeug/wrappers.py", line 81, in _iter_encoded
for item in iterable:
File "/usr/lib/python3.6/site-packages/beetsplug/web/__init__.py", line 74, in json_generator
yield json.dumps(_rep(item, expand=expand))
File "/usr/lib/python3.6/json/__init__.py", line 231, in dumps
return _default_encoder.encode(obj)
File "/usr/lib/python3.6/json/encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python3.6/json/encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "/usr/lib/python3.6/json/encoder.py", line 180, in default
o.__class__.__name__)
TypeError: Object of type 'bytes' is not JSON serializable

# Fixed code
def _rep(obj, expand=False):
    """Get a flat -- i.e., JSON-ish -- representation of a beets Item or
    Album object. For Albums, `expand` dictates whether tracks are
    included.
    """
    out = dict(obj)

    if isinstance(obj, beets.library.Item):
        if app.config.get("INCLUDE_PATHS", False):
            out["path"] = util.displayable_path(out["path"])
        else:
            del out["path"]

        # Filter all bytes attributes and convert them to strings
        for key, value in out.items():
            if isinstance(out[key], bytes):
                out[key] = base64.b64encode(out[key]).decode("ascii")

        # Get the size (in bytes) of the backing file. This is useful
        # for the Tomahawk resolver API.
        try:
            out["size"] = os.path.getsize(util.syspath(obj.path))
        except OSError:
            out["size"] = 0

        return out

    elif isinstance(obj, beets.library.Album):
        del out["artpath"]
        if expand:
            out["items"] = [_rep(item) for item in obj.items()]
        return out