Repository: modin-project/modin
Issue: pd.read_json() does not support JSON string input

### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: MacOS 10.15.4
- **Modin version** (`modin.__version__`): 0.7.2
- **Python version**:3.7.7
- **Pandas version**: 1.0.1
- **Ray version**: 0.8.0
- **Code we can use to reproduce**:
```
import modin.pandas as pd
print(pd.read_json("""[{"name": "hamx0r"}]"""))
```
Related to #554 

### Describe the problem
Even though #715 adds read_json(), it is limited to:

1. Reading JSON files, and not strings (ie fetched from a web source)
2. Each row of the JSON file has to be a complete DataFrame row's worth of data (and all columns must be present in every row).

I would like to see the ability to read JSON strings too, or at least allow some kind or workaround using BytesIO or similar. Currently, modin fails even a simple BytesIO hack:

```
import modin.pandas as pd
from io import BytesIO
j = b"""[
{"name": "hamx0r"}
]"""
bio = BytesIO(j)
df = pd.read_json(bio, lines=True)
print(df.head())
```
...results in this error:

...

>   File "...modin/engines/base/io/file_reader.py", line 15, in get_path
>     if S3_ADDRESS_REGEX.search(file_path):
> TypeError: expected string or bytes-like object

The only workaround I've found is to load with regular pandas, then convert the dataframe to Modin:

```
import pandas
import modin.pandas as pd
j = """[{"name": "hamx0r"}]"""
df = pandas.read_json(j)
df = pd.DataFrame(df)
print(df.head())
```

### Source code / logs
This traceback comes from running the "Code we can use to reproduce" above:
```
Traceback (most recent call last):
  File "code.py", line 90, in runcode
    exec(code, self.locals)
  File "<input>", line 2, in <module>
  File "/modin/pandas/io.py", line 143, in read_json
    return DataFrame(query_compiler=BaseFactory.read_json(**kwargs))
  File "/modin/data_management/factories.py", line 60, in read_json
    return cls._determine_engine()._read_json(**kwargs)
  File "/modin/data_management/factories.py", line 64, in _read_json
    return cls.io_cls.read_json(**kwargs)
  File "/modin/engines/base/io/text/json_reader.py", line 13, in read
    return cls.single_worker_read(path_or_buf, **kwargs)
  File "/modin/backends/pandas/parsers.py", line 57, in single_worker_read
    pandas_frame = cls.parse(fname, **kwargs)
  File "/modin/backends/pandas/parsers.py", line 117, in parse
    return pandas.read_json(fname, **kwargs)
  File "/pandas/util/_decorators.py", line 186, in wrapper
    return func(*args, **kwargs)
  File "/pandas/io/json/_json.py", line 608, in read_json
    result = json_reader.read()
  File "/pandas/io/json/_json.py", line 731, in read
    obj = self._get_object_parser(self.data)
  File "/pandas/io/json/_json.py", line 753, in _get_object_parser
    obj = FrameParser(json, **kwargs).parse()
  File "/pandas/io/json/_json.py", line 857, in parse
    self._parse_no_numpy()
  File "/pandas/io/json/_json.py", line 1089, in _parse_no_numpy
    loads(json, precise_float=self.precise_float), dtype=None
ValueError: Expected object or value
```

Changed files:
modin/engines/base/io/text/text_file_reader.py, modin/engines/base/io/text/csv_reader.py, modin/pandas/test/test_io.py, modin/pandas/test/utils.py, modin/engines/base/io/text/json_reader.py

diff --git a/modin/engines/base/io/text/csv_reader.py b/modin/engines/base/io/text/csv_reader.py
index ceb9d2883..5a4c1df4c 100644
--- a/modin/engines/base/io/text/csv_reader.py
+++ b/modin/engines/base/io/text/csv_reader.py
@@ -18,24 +18,6 @@ import pandas
 import sys
 
 
-def pathlib_or_pypath(filepath_or_buffer):
-    try:
-        import py
-
-        if isinstance(filepath_or_buffer, py.path.local):
-            return True
-    except ImportError:  # pragma: no cover
-        pass
-    try:
-        import pathlib
-
-        if isinstance(filepath_or_buffer, pathlib.Path):
-            return True
-    except ImportError:  # pragma: no cover
-        pass
-    return False
-
-
 class CSVReader(TextFileReader):
     @classmethod
     def read(cls, filepath_or_buffer, **kwargs):
@@ -43,7 +25,7 @@ class CSVReader(TextFileReader):
             if not cls.file_exists(filepath_or_buffer):
                 return cls.single_worker_read(filepath_or_buffer, **kwargs)
             filepath_or_buffer = cls.get_path(filepath_or_buffer)
-        elif not pathlib_or_pypath(filepath_or_buffer):
+        elif not cls.pathlib_or_pypath(filepath_or_buffer):
             return cls.single_worker_read(filepath_or_buffer, **kwargs)
         compression_type = cls.infer_compression(
             filepath_or_buffer, kwargs.get("compression")
diff --git a/modin/engines/base/io/text/json_reader.py b/modin/engines/base/io/text/json_reader.py
index 226ac801a..da3087beb 100644
--- a/modin/engines/base/io/text/json_reader.py
+++ b/modin/engines/base/io/text/json_reader.py
@@ -21,7 +21,12 @@ import numpy as np
 class JSONReader(TextFileReader):
     @classmethod
     def read(cls, path_or_buf, **kwargs):
-        path_or_buf = cls.get_path(path_or_buf)
+        if isinstance(path_or_buf, str):
+            if not cls.file_exists(path_or_buf):
+                return cls.single_worker_read(path_or_buf, **kwargs)
+            path_or_buf = cls.get_path(path_or_buf)
+        elif not cls.pathlib_or_pypath(path_or_buf):
+            return cls.single_worker_read(path_or_buf, **kwargs)
         if not kwargs.get("lines", False):
             return cls.single_worker_read(path_or_buf, **kwargs)
         columns = pandas.read_json(
diff --git a/modin/engines/base/io/text/text_file_reader.py b/modin/engines/base/io/text/text_file_reader.py
index bd86388dc..e971a4095 100644
--- a/modin/engines/base/io/text/text_file_reader.py
+++ b/modin/engines/base/io/text/text_file_reader.py
@@ -52,3 +52,21 @@ class TextFileReader(FileReader):
                 for i in range(len(partition_ids))
             ]
         )
+
+    @classmethod
+    def pathlib_or_pypath(cls, filepath_or_buffer):
+        try:
+            import py
+
+            if isinstance(filepath_or_buffer, py.path.local):
+                return True
+        except ImportError:  # pragma: no cover
+            pass
+        try:
+            import pathlib
+
+            if isinstance(filepath_or_buffer, pathlib.Path):
+                return True
+        except ImportError:  # pragma: no cover
+            pass
+        return False
diff --git a/modin/pandas/test/test_io.py b/modin/pandas/test/test_io.py
index c1c219442..15add96f3 100644
--- a/modin/pandas/test/test_io.py
+++ b/modin/pandas/test/test_io.py
@@ -23,7 +23,13 @@ import os
 import shutil
 import sqlalchemy as sa
 
-from .utils import df_equals
+from .utils import (
+    df_equals,
+    json_short_string,
+    json_short_bytes,
+    json_long_string,
+    json_long_bytes,
+)
 
 from modin import __execution_engine__
 
@@ -456,6 +462,18 @@ def test_from_json_lines():
     teardown_json_file()
 
 
+@pytest.mark.parametrize(
+    "data", [json_short_string, json_short_bytes, json_long_string, json_long_bytes],
+)
+def test_read_json_string_bytes(data):
+    with pytest.warns(UserWarning):
+        modin_df = pd.read_json(data)
+    # For I/O objects we need to rewind to reuse the same object.
+    if hasattr(data, "seek"):
+        data.seek(0)
+    df_equals(modin_df, pandas.read_json(data))
+
+
 def test_from_html():
     setup_html_file(SMALL_ROW_SIZE)
 
diff --git a/modin/pandas/test/utils.py b/modin/pandas/test/utils.py
index 1c517a6fb..1faaf386a 100644
--- a/modin/pandas/test/utils.py
+++ b/modin/pandas/test/utils.py
@@ -16,6 +16,7 @@ import pandas
 from pandas.util.testing import assert_almost_equal, assert_frame_equal
 import modin.pandas as pd
 from modin.pandas.utils import to_pandas
+from io import BytesIO
 
 random_state = np.random.RandomState(seed=42)
 
@@ -300,6 +301,48 @@ int_arg_values = list(int_arg.values())
 
 # END parametrizations of common kwargs
 
+json_short_string = """[{"project": "modin"}]"""
+json_long_string = """{
+        "quiz": {
+            "sport": {
+                "q1": {
+                    "question": "Which one is correct team name in NBA?",
+                    "options": [
+                        "New York Bulls",
+                        "Los Angeles Kings",
+                        "Golden State Warriros",
+                        "Huston Rocket"
+                    ],
+                    "answer": "Huston Rocket"
+                }
+            },
+            "maths": {
+                "q1": {
+                    "question": "5 + 7 = ?",
+                    "options": [
+                        "10",
+                        "11",
+                        "12",
+                        "13"
+                    ],
+                    "answer": "12"
+                },
+                "q2": {
+                    "question": "12 - 8 = ?",
+                    "options": [
+                        "1",
+                        "2",
+                        "3",
+                        "4"
+                    ],
+                    "answer": "4"
+                }
+            }
+        }
+    }"""
+json_long_bytes = BytesIO(json_long_string.encode(encoding="UTF-8"))
+json_short_bytes = BytesIO(json_short_string.encode(encoding="UTF-8"))
+
 
 def df_equals(df1, df2):
     """Tests if df1 and df2 are equal.