json2xml 7.1.0__tar.gz → 7.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {json2xml-7.1.0 → json2xml-7.1.1}/HISTORY.rst +7 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/PKG-INFO +1 -1
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml/__init__.py +1 -1
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml/utils.py +9 -6
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml.egg-info/PKG-INFO +1 -1
- {json2xml-7.1.0 → json2xml-7.1.1}/pyproject.toml +1 -1
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/test_cli.py +32 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/test_dict2xml.py +2 -2
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/test_dicttoxml_unit.py +3 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/test_json2xml.py +13 -12
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/test_missing_coverage.py +3 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/test_utils.py +44 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/AUTHORS.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/CONTRIBUTING.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/LICENSE +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/MANIFEST.in +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/README.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/Makefile +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/authors.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/benchmarks.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/conf.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/contributing.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/history.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/index.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/installation.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/json2xml.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/make.bat +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/modules.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/readme.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/rust_memory_benchmark.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/docs/usage.rst +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml/backend_selector.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml/cli.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml/dicttoxml.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml/dicttoxml_fast.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml/json2xml.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml/types.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml.egg-info/SOURCES.txt +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml.egg-info/dependency_links.txt +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml.egg-info/entry_points.txt +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml.egg-info/requires.txt +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/json2xml.egg-info/top_level.txt +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/setup.cfg +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/setup.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/__init__.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/conftest.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/test_backend_selector.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/test_benchmark_multi_python.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/test_benchmark_security_hardening.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/test_dicttoxml_fast_fallback.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/test_rust_dicttoxml.py +0 -0
- {json2xml-7.1.0 → json2xml-7.1.1}/tests/test_rust_python_parity.py +0 -0
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
|
|
2
|
+
7.1.1 / 2026-09-23
|
|
3
|
+
==================
|
|
4
|
+
|
|
5
|
+
* fix: include the JSON decoder's line and column in ``JSONReadError``, ``StringReadError``, and ``URLReadError`` messages, so the CLI reports where the input breaks instead of only that it is invalid
|
|
6
|
+
* fix: accept a UTF-8 byte order mark in file, URL, string, and stdin JSON input
|
|
7
|
+
* chore: fix the diagnostics ty 0.0.83 reports in tests and make the list-omission test compare its result
|
|
8
|
+
|
|
2
9
|
7.1.0 / 2026-09-03
|
|
3
10
|
==================
|
|
4
11
|
|
|
@@ -65,12 +65,14 @@ def _effective_port(parsed: SplitResult) -> int:
|
|
|
65
65
|
def readfromjson(filename: str) -> JSONValue:
|
|
66
66
|
"""Read JSON data from a file."""
|
|
67
67
|
try:
|
|
68
|
-
|
|
68
|
+
# utf-8-sig drops the BOM that Windows tools write by default.
|
|
69
|
+
with open(filename, encoding="utf-8-sig") as jsondata:
|
|
69
70
|
return json.load(jsondata)
|
|
70
71
|
except OSError as error:
|
|
71
72
|
raise JSONReadError("Could not read JSON file") from error
|
|
72
73
|
except ValueError as error:
|
|
73
|
-
|
|
74
|
+
# Keep the decoder message so callers can show where parsing stopped.
|
|
75
|
+
raise JSONReadError(f"Invalid JSON File: {error}") from error
|
|
74
76
|
|
|
75
77
|
|
|
76
78
|
# @lat: [[behavior#URL security boundaries]]
|
|
@@ -349,9 +351,9 @@ def readfromurl(
|
|
|
349
351
|
response.close()
|
|
350
352
|
|
|
351
353
|
try:
|
|
352
|
-
return json.loads(response_data.decode("utf-8"))
|
|
354
|
+
return json.loads(response_data.decode("utf-8-sig"))
|
|
353
355
|
except (UnicodeDecodeError, json.JSONDecodeError) as error:
|
|
354
|
-
raise URLReadError("URL did not return valid JSON") from error
|
|
356
|
+
raise URLReadError(f"URL did not return valid JSON: {error}") from error
|
|
355
357
|
|
|
356
358
|
|
|
357
359
|
def readfromstring(jsondata: object) -> JSONValue:
|
|
@@ -359,6 +361,7 @@ def readfromstring(jsondata: object) -> JSONValue:
|
|
|
359
361
|
if not isinstance(jsondata, str):
|
|
360
362
|
raise StringReadError("Input is not a proper JSON string")
|
|
361
363
|
try:
|
|
362
|
-
|
|
364
|
+
# Text sources such as stdin deliver a BOM as a leading U+FEFF.
|
|
365
|
+
return json.loads(jsondata.removeprefix("\ufeff"))
|
|
363
366
|
except ValueError as error:
|
|
364
|
-
raise StringReadError("Input is not a proper JSON string") from error
|
|
367
|
+
raise StringReadError(f"Input is not a proper JSON string: {error}") from error
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import io
|
|
6
|
+
import re
|
|
6
7
|
import subprocess
|
|
7
8
|
import sys
|
|
8
9
|
import tempfile
|
|
@@ -404,6 +405,37 @@ class TestCLI:
|
|
|
404
405
|
assert "Could not parse JSON file" in result.stderr
|
|
405
406
|
assert str(json_file) in result.stderr
|
|
406
407
|
|
|
408
|
+
# @lat: [[tests#Input readers#Invalid JSON errors include the decoder position]]
|
|
409
|
+
def test_invalid_json_file_reports_position(self) -> None:
|
|
410
|
+
"""The CLI shows where the decoder stopped, not only that it failed."""
|
|
411
|
+
json_file = Path(__file__).parent.parent / "examples" / "wrongjson.json"
|
|
412
|
+
|
|
413
|
+
result = subprocess.run(
|
|
414
|
+
[sys.executable, "-m", "json2xml.cli", str(json_file)],
|
|
415
|
+
capture_output=True,
|
|
416
|
+
text=True,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
assert result.returncode == 1
|
|
420
|
+
assert "Could not parse JSON file" in result.stderr
|
|
421
|
+
# CPython and PyPy report different positions for the same file.
|
|
422
|
+
assert re.search(r"line \d+ column \d+", result.stderr)
|
|
423
|
+
|
|
424
|
+
def test_json_file_with_utf8_bom(self) -> None:
|
|
425
|
+
"""A BOM-prefixed file converts instead of being reported as invalid."""
|
|
426
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
427
|
+
json_file = Path(tmpdir) / "bom.json"
|
|
428
|
+
json_file.write_bytes(b'\xef\xbb\xbf{"bom": true}')
|
|
429
|
+
|
|
430
|
+
result = subprocess.run(
|
|
431
|
+
[sys.executable, "-m", "json2xml.cli", str(json_file)],
|
|
432
|
+
capture_output=True,
|
|
433
|
+
text=True,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
assert result.returncode == 0, result.stderr
|
|
437
|
+
assert '<bom type="bool">true</bom>' in result.stdout
|
|
438
|
+
|
|
407
439
|
def test_output_file_permission_error(self) -> None:
|
|
408
440
|
"""Test error handling when output file cannot be written."""
|
|
409
441
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
@@ -190,8 +190,8 @@ class TestDict2xml:
|
|
|
190
190
|
def test_dict2xml_omit_list(self) -> None:
|
|
191
191
|
"""Test dicttoxml with list omission."""
|
|
192
192
|
obj = {"list": [{"bike": "blue"}, {"wheel": "black"}]}
|
|
193
|
-
dicttoxml.dicttoxml(obj, root=False, attr_type=False, item_wrap=False)
|
|
194
|
-
assert b"<list><bike>blue</bike><wheel>black</wheel></list>"
|
|
193
|
+
result = dicttoxml.dicttoxml(obj, root=False, attr_type=False, item_wrap=False)
|
|
194
|
+
assert b"<list><bike>blue</bike><wheel>black</wheel></list>" == result
|
|
195
195
|
|
|
196
196
|
def test_dict2xml_with_val_and_custom_attr(self) -> None:
|
|
197
197
|
"""Test dicttoxml with @val and custom attributes."""
|
|
@@ -12,6 +12,7 @@ import xmltodict
|
|
|
12
12
|
|
|
13
13
|
from json2xml import json2xml
|
|
14
14
|
from json2xml.json2xml import _positive_limit
|
|
15
|
+
from json2xml.types import JSONValue
|
|
15
16
|
from json2xml.utils import (
|
|
16
17
|
InvalidDataError,
|
|
17
18
|
JSONReadError,
|
|
@@ -215,7 +216,7 @@ class TestJson2xml:
|
|
|
215
216
|
assert "dict" == old_dict["all"]["empty_dict"]["@type"]
|
|
216
217
|
|
|
217
218
|
def test_dicttoxml_bug(self) -> None:
|
|
218
|
-
input_dict = {
|
|
219
|
+
input_dict: JSONValue = {
|
|
219
220
|
"response": {
|
|
220
221
|
"results": {
|
|
221
222
|
"user": [
|
|
@@ -342,7 +343,7 @@ class TestJson2xml:
|
|
|
342
343
|
|
|
343
344
|
def test_pretty_output_limit_counts_indentation(self) -> None:
|
|
344
345
|
"""Pretty-only whitespace is included in the exact output byte budget."""
|
|
345
|
-
data = {"outer": {"inner": {"leaf": "value"}}}
|
|
346
|
+
data: JSONValue = {"outer": {"inner": {"leaf": "value"}}}
|
|
346
347
|
compact_size = len(json2xml.Json2xml(data).to_xml() or b"")
|
|
347
348
|
|
|
348
349
|
with pytest.raises(InvalidDataError, match="XML output size limit exceeded"):
|
|
@@ -430,7 +431,7 @@ class TestJson2xml:
|
|
|
430
431
|
assert dict_from_xml["all"]["string_array"]["item"][2]["#text"] == "c"
|
|
431
432
|
|
|
432
433
|
def test_dict_attr_crash(self) -> None:
|
|
433
|
-
data = {
|
|
434
|
+
data: JSONValue = {
|
|
434
435
|
"product": {
|
|
435
436
|
"@attrs": {"attr_name": "attr_value", "a": "b"},
|
|
436
437
|
"@val": [],
|
|
@@ -461,7 +462,7 @@ class TestJson2xml:
|
|
|
461
462
|
# @lat: [[tests#Conversion behavior#XPath format adds functions namespace]]
|
|
462
463
|
def test_xpath_format_basic(self) -> None:
|
|
463
464
|
"""Test XPath 3.1 json-to-xml format with basic types."""
|
|
464
|
-
data = {"name": "John", "age": 30, "active": True}
|
|
465
|
+
data: JSONValue = {"name": "John", "age": 30, "active": True}
|
|
465
466
|
xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml()
|
|
466
467
|
assert isinstance(xmldata, bytes)
|
|
467
468
|
assert b'xmlns="http://www.w3.org/2005/xpath-functions"' in xmldata
|
|
@@ -471,7 +472,7 @@ class TestJson2xml:
|
|
|
471
472
|
|
|
472
473
|
def test_xpath_format_nested_dict(self) -> None:
|
|
473
474
|
"""Test XPath 3.1 format with nested dictionaries."""
|
|
474
|
-
data = {"person": {"name": "Alice", "age": 25}}
|
|
475
|
+
data: JSONValue = {"person": {"name": "Alice", "age": 25}}
|
|
475
476
|
xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml()
|
|
476
477
|
assert isinstance(xmldata, bytes)
|
|
477
478
|
assert b'<map key="person">' in xmldata
|
|
@@ -480,7 +481,7 @@ class TestJson2xml:
|
|
|
480
481
|
|
|
481
482
|
def test_xpath_format_array(self) -> None:
|
|
482
483
|
"""Test XPath 3.1 format with arrays."""
|
|
483
|
-
data = {"numbers": [1, 2, 3]}
|
|
484
|
+
data: JSONValue = {"numbers": [1, 2, 3]}
|
|
484
485
|
xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml()
|
|
485
486
|
assert isinstance(xmldata, bytes)
|
|
486
487
|
assert b'<array key="numbers">' in xmldata
|
|
@@ -490,14 +491,14 @@ class TestJson2xml:
|
|
|
490
491
|
|
|
491
492
|
def test_xpath_format_null(self) -> None:
|
|
492
493
|
"""Test XPath 3.1 format with null values."""
|
|
493
|
-
data = {"value": None}
|
|
494
|
+
data: JSONValue = {"value": None}
|
|
494
495
|
xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml()
|
|
495
496
|
assert isinstance(xmldata, bytes)
|
|
496
497
|
assert b'<null key="value"/>' in xmldata
|
|
497
498
|
|
|
498
499
|
def test_xpath_format_mixed_array(self) -> None:
|
|
499
500
|
"""Test XPath 3.1 format with mixed type arrays."""
|
|
500
|
-
data = {"items": ["text", 42, True, None]}
|
|
501
|
+
data: JSONValue = {"items": ["text", 42, True, None]}
|
|
501
502
|
xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml()
|
|
502
503
|
assert isinstance(xmldata, bytes)
|
|
503
504
|
assert b'<array key="items">' in xmldata
|
|
@@ -508,7 +509,7 @@ class TestJson2xml:
|
|
|
508
509
|
|
|
509
510
|
def test_xpath_format_complex_nested(self) -> None:
|
|
510
511
|
"""Test XPath 3.1 format with complex nested structures."""
|
|
511
|
-
data = {
|
|
512
|
+
data: JSONValue = {
|
|
512
513
|
"content": [
|
|
513
514
|
{"id": 70805774, "value": "1001", "position": [1004.0, 288.0]},
|
|
514
515
|
]
|
|
@@ -523,7 +524,7 @@ class TestJson2xml:
|
|
|
523
524
|
|
|
524
525
|
def test_xpath_format_escaping(self) -> None:
|
|
525
526
|
"""Test XPath 3.1 format properly escapes special characters."""
|
|
526
|
-
data = {"text": "<script>alert('xss')</script>"}
|
|
527
|
+
data: JSONValue = {"text": "<script>alert('xss')</script>"}
|
|
527
528
|
xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml()
|
|
528
529
|
assert isinstance(xmldata, bytes)
|
|
529
530
|
assert b"<script>" in xmldata
|
|
@@ -531,7 +532,7 @@ class TestJson2xml:
|
|
|
531
532
|
|
|
532
533
|
def test_xpath_format_with_pretty_print(self) -> None:
|
|
533
534
|
"""Test XPath 3.1 format works with pretty printing."""
|
|
534
|
-
data = {"name": "Test"}
|
|
535
|
+
data: JSONValue = {"name": "Test"}
|
|
535
536
|
xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=True).to_xml()
|
|
536
537
|
assert isinstance(xmldata, str)
|
|
537
538
|
assert 'xmlns="http://www.w3.org/2005/xpath-functions"' in xmldata
|
|
@@ -539,7 +540,7 @@ class TestJson2xml:
|
|
|
539
540
|
|
|
540
541
|
def test_xpath_format_root_array(self) -> None:
|
|
541
542
|
"""Test XPath 3.1 format with root-level array."""
|
|
542
|
-
data = [1, 2, 3]
|
|
543
|
+
data: JSONValue = [1, 2, 3]
|
|
543
544
|
xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml()
|
|
544
545
|
assert isinstance(xmldata, bytes)
|
|
545
546
|
assert b'<array xmlns="http://www.w3.org/2005/xpath-functions">' in xmldata
|
|
@@ -7,6 +7,7 @@ import tempfile
|
|
|
7
7
|
import threading
|
|
8
8
|
import zlib
|
|
9
9
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
10
|
+
from pathlib import Path
|
|
10
11
|
from typing import TYPE_CHECKING, Any, ClassVar, cast
|
|
11
12
|
from unittest.mock import Mock, patch
|
|
12
13
|
|
|
@@ -34,6 +35,7 @@ class JsonTestHandler(BaseHTTPRequestHandler):
|
|
|
34
35
|
"/data.json": (200, b'{"key": "value", "number": 42}'),
|
|
35
36
|
"/api": (200, b'{"result": "success"}'),
|
|
36
37
|
"/invalid.json": (200, b"invalid json content"),
|
|
38
|
+
"/bom.json": (200, b'\xef\xbb\xbf{"bom": true}'),
|
|
37
39
|
"/error.json": (500, b'{"error": true}'),
|
|
38
40
|
"/api.json": (200, b'{"api": "response", "status": "ok"}'),
|
|
39
41
|
}
|
|
@@ -128,6 +130,28 @@ class TestReadFromJson:
|
|
|
128
130
|
|
|
129
131
|
os.unlink(temp_filename)
|
|
130
132
|
|
|
133
|
+
def test_readfromjson_reports_decoder_position(self) -> None:
|
|
134
|
+
"""The wrapped error carries the decoder's line and column."""
|
|
135
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
136
|
+
f.write('{"a": 1 "b": 2}')
|
|
137
|
+
temp_filename = f.name
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
with pytest.raises(JSONReadError, match="line 1 column 9"):
|
|
141
|
+
readfromjson(temp_filename)
|
|
142
|
+
finally:
|
|
143
|
+
import os
|
|
144
|
+
|
|
145
|
+
os.unlink(temp_filename)
|
|
146
|
+
|
|
147
|
+
# @lat: [[tests#Input readers#Readers accept a UTF-8 byte order mark]]
|
|
148
|
+
def test_readfromjson_accepts_utf8_bom(self, tmp_path: Path) -> None:
|
|
149
|
+
"""A leading UTF-8 BOM, as written by Windows tools, is not an error."""
|
|
150
|
+
bom_file = tmp_path / "bom.json"
|
|
151
|
+
bom_file.write_bytes(b'\xef\xbb\xbf{"bom": true}')
|
|
152
|
+
|
|
153
|
+
assert readfromjson(str(bom_file)) == {"bom": True}
|
|
154
|
+
|
|
131
155
|
# @lat: [[tests#Input readers#File reader distinguishes unreadable files from invalid JSON]]
|
|
132
156
|
def test_readfromjson_file_not_found(self) -> None:
|
|
133
157
|
"""Test reading a non-existent file."""
|
|
@@ -186,6 +210,17 @@ class TestReadFromUrl:
|
|
|
186
210
|
with pytest.raises(URLReadError, match="URL did not return valid JSON"):
|
|
187
211
|
readfromurl(f"{json_server}/invalid.json", allow_private_networks=True)
|
|
188
212
|
|
|
213
|
+
def test_readfromurl_reports_decoder_position(self, json_server: str) -> None:
|
|
214
|
+
"""The wrapped error carries the decoder's message."""
|
|
215
|
+
with pytest.raises(URLReadError, match=r"URL did not return valid JSON: .+line 1"):
|
|
216
|
+
readfromurl(f"{json_server}/invalid.json", allow_private_networks=True)
|
|
217
|
+
|
|
218
|
+
def test_readfromurl_accepts_utf8_bom(self, json_server: str) -> None:
|
|
219
|
+
"""A BOM-prefixed response body decodes like any other UTF-8 body."""
|
|
220
|
+
result = readfromurl(f"{json_server}/bom.json", allow_private_networks=True)
|
|
221
|
+
|
|
222
|
+
assert result == {"bom": True}
|
|
223
|
+
|
|
189
224
|
def test_readfromurl_network_error(self) -> None:
|
|
190
225
|
"""Test network failures are wrapped as URLReadError."""
|
|
191
226
|
with socket.socket() as unused_socket:
|
|
@@ -715,6 +750,15 @@ class TestReadFromString:
|
|
|
715
750
|
}
|
|
716
751
|
assert result == expected
|
|
717
752
|
|
|
753
|
+
def test_readfromstring_reports_decoder_position(self) -> None:
|
|
754
|
+
"""The wrapped error carries the decoder's line and column."""
|
|
755
|
+
with pytest.raises(StringReadError, match="line 1 column 9"):
|
|
756
|
+
readfromstring('{"a": 1 "b": 2}')
|
|
757
|
+
|
|
758
|
+
def test_readfromstring_accepts_utf8_bom(self) -> None:
|
|
759
|
+
"""A leading U+FEFF, as stdin delivers a BOM, is dropped before parsing."""
|
|
760
|
+
assert readfromstring('\ufeff{"bom": true}') == {"bom": True}
|
|
761
|
+
|
|
718
762
|
def test_readfromstring_invalid_type_int(self) -> None:
|
|
719
763
|
"""Test reading with integer input."""
|
|
720
764
|
with pytest.raises(StringReadError, match="Input is not a proper JSON string"):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|