Repository: mitmproxy/mitmproxy

Issue:
`Couldn't parse: falling back to Raw` for empty JSON array
#### Problem Description
The following response packet:

```
HTTP/2.0 200 
date: Sun, 14 Jan 2024 23:30:12 GMT
content-type: application/json; charset=utf-8
content-length: 2
server: nginx/1.18.0 (Ubuntu)
cache-control: no-store
x-amzn-trace-id: [REDACTED]
x-frame-options: DENY
set-cookie: [REDACTED]
strict-transport-security: max-age=31536000

[]
```

is displayed as "Couldn't parse: falling back to Raw", but it's clearly JSON from the content type and an empty array is a normal JSON response that shouldn't trigger this warning. In fact, top level `null`, booleans, numbers and strings should be just fine as well, I don't know if they do or don't.

#### Steps to reproduce the behavior:
Set up a JSON endpoint that returns `[]` with a `content-type: application/json; charset=utf-8` header and open it in mitmproxy

#### System Information
```
Mitmproxy: 10.2.1 binary
Python:    3.12.1
OpenSSL:   OpenSSL 3.1.4 24 Oct 2023
Platform:  macOS-14.2.1-x86_64-arm-64bit
```


Discussion:
Thanks! Looks like an edge case for the empty array, the following all work: `[1]`, `"foo"` and `null`
@Prinzhorn I would like to fix this. Could you show me the part(s) of the codebase where this is handled?
@haanhvu sure, it's probably somewhere in https://github.com/mitmproxy/mitmproxy/blob/main/mitmproxy/contentviews/json.py . I don't think the parsing of the JSON is failing but the algorithm that colorizes it (`format_json`).

I'd start with adding a failing test to https://github.com/mitmproxy/mitmproxy/blob/main/test/mitmproxy/contentviews/test_json.py

Edit: here's how you locally set everything up https://github.com/mitmproxy/mitmproxy/blob/main/CONTRIBUTING.md

Resolving patch:
diff --git a/mitmproxy/contentviews/graphql.py b/mitmproxy/contentviews/graphql.py
--- a/mitmproxy/contentviews/graphql.py
+++ b/mitmproxy/contentviews/graphql.py
@@ -30,7 +30,12 @@ def is_graphql_query(data):
 
 
 def is_graphql_batch_query(data):
-    return isinstance(data, list) and isinstance(data[0], dict) and "query" in data[0]
+    return (
+        isinstance(data, list)
+        and len(data) > 0
+        and isinstance(data[0], dict)
+        and "query" in data[0]
+    )
 
 
 class ViewGraphQL(base.View):


Test patch:
diff --git a/test/mitmproxy/contentviews/test_graphql.py b/test/mitmproxy/contentviews/test_graphql.py
--- a/test/mitmproxy/contentviews/test_graphql.py
+++ b/test/mitmproxy/contentviews/test_graphql.py
@@ -19,6 +19,7 @@ def test_render_priority():
     assert 0 == v.render_priority(
         b"""[{"xquery": "query P { \\n }"}]""", content_type="application/json"
     )
+    assert 0 == v.render_priority(b"""[]""", content_type="application/json")
     assert 0 == v.render_priority(b"}", content_type="application/json")
 
 
diff --git a/test/mitmproxy/contentviews/test_json.py b/test/mitmproxy/contentviews/test_json.py
--- a/test/mitmproxy/contentviews/test_json.py
+++ b/test/mitmproxy/contentviews/test_json.py
@@ -78,6 +78,22 @@ def test_format_json():
         [("text", "    "), ("text", "]"), ("text", "")],
         [("text", ""), ("text", "}")],
     ]
+    assert list(json.format_json({"list": []})) == [
+        [("text", "{"), ("text", "")],
+        [
+            ("text", "    "),
+            ("Token_Name_Tag", '"list"'),
+            ("text", ": "),
+            ("text", "[]"),
+            ("text", ""),
+        ],
+        [("text", ""), ("text", "}")],
+    ]
+    assert list(json.format_json(None)) == [[("Token_Keyword_Constant", "null")]]
+    assert list(json.format_json(True)) == [[("Token_Keyword_Constant", "true")]]
+    assert list(json.format_json(1)) == [[("Token_Literal_Number", "1")]]
+    assert list(json.format_json("test")) == [[("Token_Literal_String", '"test"')]]
+    assert list(json.format_json([])) == [[("text", "[]")]]
 
 
 def test_view_json():
@@ -88,6 +104,7 @@ def test_view_json():
     assert v(b"[1, 2, 3, 4, 5]")
     assert v(b'{"foo" : 3}')
     assert v(b'{"foo": true, "nullvalue": null}')
+    assert v(b"[]")
 
 
 @given(binary())