Repository: mitmproxy/mitmproxy
Pull request: Fix `Couldn't parse: falling back to Raw` for empty JSON array
#### Description

fix #6603 

#### Checklist

 - [x] I have updated tests where applicable.
 - [ ] I have added an entry to the CHANGELOG.


Problem statement:
`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
```

Hints from issue 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
> I'd start with adding a failing test to https://github.com/mitmproxy/mitmproxy/blob/main/test/mitmproxy/contentviews/test_json.py

@Prinzhorn I meant to add failing tests but somehow they all passed^^ Could you take a look at #6619 to see if I wrote the tests correctly?

Commit message: Reorder condition

Patch to review:
diff --git a/mitmproxy/contentviews/graphql.py b/mitmproxy/contentviews/graphql.py
index 5263b75f5f..4916197bb1 100644
--- a/mitmproxy/contentviews/graphql.py
+++ b/mitmproxy/contentviews/graphql.py
@@ -30,7 +30,7 @@ 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):
diff --git a/test/mitmproxy/contentviews/test_graphql.py b/test/mitmproxy/contentviews/test_graphql.py
index 89beda814c..31107343ec 100644
--- a/test/mitmproxy/contentviews/test_graphql.py
+++ b/test/mitmproxy/contentviews/test_graphql.py
@@ -19,6 +19,9 @@ 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
index 9711a64659..fbb1332853 100644
--- a/test/mitmproxy/contentviews/test_json.py
+++ b/test/mitmproxy/contentviews/test_json.py
@@ -78,6 +78,16 @@ 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 +98,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())


Code review comments:
diff_hunk: @@ -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 (
+        len(data) > 0
path: mitmproxy/contentviews/graphql.py
text: @author:
Yeah missed that case. I changed it back to your order.