Repository: frappe/frappe

Issue:
json_handler is not flexible enough to handle arbitrary objects
## Context

I plugged frappe/insights into a Postgres database used by another _(non-Frappe)_ application. The postgres client returned `uuid.UUID` objects and given that there's no custom serializer,  a TypeError was raised.

## Context information (for bug reports)

**Reproducible Versions**

```
v15 & develop
```

### Stacktrace / full error message

```python
TypeError: Object of type <class 'uuid.UUID'> with value of UUID('c7b74973-e9f2-416f-a818-4293cd91cbab') is not JSON serializable
  File "__init__.py", line 2011, in as_json
    Alias for `frappe.db.get_value`
  File "__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "json/encoder.py", line 202, in encode
    chunks = list(chunks)
  File "json/encoder.py", line 430, in _iterencode
    yield from _iterencode_list(o, _current_indent_level)
  File "json/encoder.py", line 326, in _iterencode_list
    yield from chunks
  File "json/encoder.py", line 326, in _iterencode_list
    yield from chunks
  File "json/encoder.py", line 439, in _iterencode
    o = _default(o)
  File "frappe/utils/response.py", line 235, in json_handler
    raise TypeError(f"""Object of type {type(obj)} with value of {obj!r} is not JSON serializable""")
AttributeError: 'list' object has no attribute 'items'
  File "frappe/app.py", line 110, in application
    response = frappe.api.handle(request)
  File "frappe/api/__init__.py", line 49, in handle
    data = endpoint(**arguments)
  File "frappe/api/v1.py", line 36, in handle_rpc_call
    return frappe.handler.handle()
  File "frappe/handler.py", line 49, in handle
    data = execute_cmd(cmd)
  File "frappe/handler.py", line 85, in execute_cmd
    return frappe.call(method, **frappe.form_dict)
  File "__init__.py", line 1697, in call
    
  File "frappe/handler.py", line 327, in run_doc_method
    response = doc.run_method(method)
  File "frappe/model/document.py", line 955, in run_method
    if method in self.__dict__ or callable(method_object):
  File "frappe/model/document.py", line 1315, in composer
    ):
  File "frappe/model/document.py", line 1297, in runner
    self._return_value = new_return_value
  File "frappe/model/document.py", line 952, in fn
    
  File "frappe/utils/typing_validations.py", line 31, in wrapper
    return func(*args, **kwargs)
  File "insights/insights/doctype/insights_table/insights_table.py", line 28, in get_preview
    return data_source.get_table_preview(self.table)
  File "insights/insights/doctype/insights_data_source/insights_data_source.py", line 201, in get_table_preview
    return self._db.get_table_preview(table, limit)
  File "insights/insights/doctype/insights_data_source/sources/postgresql.py", line 127, in get_table_preview
    data = self.execute_query(f"""select * from "{table}" limit {limit}""", cached=True)
  File "insights/insights/doctype/insights_data_source/sources/base_database.py", line 107, in execute_query
    cached and cache_results(sql, self.data_source, ret)
  File "insights/insights/doctype/insights_data_source/sources/utils.py", line 289, in cache_results
    frappe.as_json(results),
  File "__init__.py", line 2022, in as_json
```

## Potential Solutions

1. Add yet another clause to check if UUID then str(obj) and wait for future failures and keep adding types and handling logic over the next few years like https://github.com/frappe/frappe/pull/19413
2. Resolve everything to str(obj) as the last resort instead of throwing a `TypeError`; additionally, allow apps to extend json_handler to override behaviour (?)


Discussion:
Adding UUID handler + hooks for arbitrary types makes sense. 

str can be potentially weird because it's just human representation, repr might be fine as default fallback (?)

Resolving patch:
diff --git a/frappe/utils/response.py b/frappe/utils/response.py
--- a/frappe/utils/response.py
+++ b/frappe/utils/response.py
@@ -7,6 +7,7 @@
 import mimetypes
 import os
 import sys
+import uuid
 from typing import TYPE_CHECKING
 from urllib.parse import quote
 
@@ -231,6 +232,9 @@ def json_handler(obj):
 	elif callable(obj):
 		return repr(obj)
 
+	elif isinstance(obj, uuid.UUID):
+		return str(obj)
+
 	else:
 		raise TypeError(f"""Object of type {type(obj)} with value of {obj!r} is not JSON serializable""")
 


Test patch: