json-repair 0.29.0__tar.gz → 0.29.2__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: json_repair
3
- Version: 0.29.0
3
+ Version: 0.29.2
4
4
  Summary: A package to repair broken json strings
5
5
  Author-email: Stefano Baccianella <4247706+mangiucugna@users.noreply.github.com>
6
6
  License: MIT License
@@ -156,24 +156,24 @@ Install the library for command-line with:
156
156
  ```
157
157
  pipx install json-repair
158
158
  ```
159
- then run
159
+ to know all options available:
160
160
  ```
161
161
  $ json_repair -h
162
-
163
- usage: json_repair [-h] [-i] [--ensure_ascii] [--indent INDENT] filename
162
+ usage: json_repair [-h] [-i] [-o TARGET] [--ensure_ascii] [--indent INDENT] filename
164
163
 
165
164
  Repair and parse JSON files.
166
165
 
167
166
  positional arguments:
168
- filename The JSON file to repair
167
+ filename The JSON file to repair
169
168
 
170
169
  options:
171
- -h, --help show this help message and exit
172
- -i, --inline Replace the file inline instead of returning the output to stdout
173
- --ensure_ascii Pass the ensure_ascii parameter to json.dumps()
174
- --indent INDENT Number of spaces for indentation (Default 2)
170
+ -h, --help show this help message and exit
171
+ -i, --inline Replace the file inline instead of returning the output to stdout
172
+ -o TARGET, --output TARGET
173
+ If specified, the output will be written to TARGET filename instead of stdout
174
+ --ensure_ascii Pass ensure_ascii=True to json.dumps()
175
+ --indent INDENT Number of spaces for indentation (Default 2)
175
176
  ```
176
- to learn how to use it
177
177
 
178
178
  ## Adding to requirements
179
179
  **Please pin this library only on the major version!**
@@ -118,24 +118,24 @@ Install the library for command-line with:
118
118
  ```
119
119
  pipx install json-repair
120
120
  ```
121
- then run
121
+ to know all options available:
122
122
  ```
123
123
  $ json_repair -h
124
-
125
- usage: json_repair [-h] [-i] [--ensure_ascii] [--indent INDENT] filename
124
+ usage: json_repair [-h] [-i] [-o TARGET] [--ensure_ascii] [--indent INDENT] filename
126
125
 
127
126
  Repair and parse JSON files.
128
127
 
129
128
  positional arguments:
130
- filename The JSON file to repair
129
+ filename The JSON file to repair
131
130
 
132
131
  options:
133
- -h, --help show this help message and exit
134
- -i, --inline Replace the file inline instead of returning the output to stdout
135
- --ensure_ascii Pass the ensure_ascii parameter to json.dumps()
136
- --indent INDENT Number of spaces for indentation (Default 2)
132
+ -h, --help show this help message and exit
133
+ -i, --inline Replace the file inline instead of returning the output to stdout
134
+ -o TARGET, --output TARGET
135
+ If specified, the output will be written to TARGET filename instead of stdout
136
+ --ensure_ascii Pass ensure_ascii=True to json.dumps()
137
+ --indent INDENT Number of spaces for indentation (Default 2)
137
138
  ```
138
- to learn how to use it
139
139
 
140
140
  ## Adding to requirements
141
141
  **Please pin this library only on the major version!**
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.0"]
3
3
  build-backend = "setuptools.build_meta"
4
4
  [project]
5
5
  name = "json_repair"
6
- version = "0.29.0"
6
+ version = "0.29.2"
7
7
  license = {file = "LICENSE"}
8
8
  authors = [
9
9
  { name="Stefano Baccianella", email="4247706+mangiucugna@users.noreply.github.com" },
@@ -384,38 +384,39 @@ class JSONParser:
384
384
  # * If we are fixing missing quotes in an object, when it finds the special terminators
385
385
  char = self.get_char_at()
386
386
  while char and char != rstring_delimiter:
387
- if missing_quotes:
388
- if self.get_context() == "object_key" and (
389
- char == ":" or char.isspace()
390
- ):
387
+ if (
388
+ missing_quotes
389
+ and self.get_context() == "object_key"
390
+ and (char == ":" or char.isspace())
391
+ ):
392
+ self.log(
393
+ "While parsing a string missing the left delimiter in object key context, we found a :, stopping here",
394
+ "info",
395
+ )
396
+ break
397
+ if self.get_context() == "object_value" and char in [",", "}"]:
398
+ rstring_delimiter_missing = True
399
+ # check if this is a case in which the closing comma is NOT missing instead
400
+ i = 1
401
+ next_c = self.get_char_at(i)
402
+ while next_c and next_c != rstring_delimiter:
403
+ i += 1
404
+ next_c = self.get_char_at(i)
405
+ if next_c:
406
+ i += 1
407
+ next_c = self.get_char_at(i)
408
+ # found a delimiter, now we need to check that is followed strictly by a comma or brace
409
+ while next_c and next_c.isspace():
410
+ i += 1
411
+ next_c = self.get_char_at(i)
412
+ if next_c and next_c in [",", "}"]:
413
+ rstring_delimiter_missing = False
414
+ if rstring_delimiter_missing:
391
415
  self.log(
392
- "While parsing a string missing the left delimiter in object key context, we found a :, stopping here",
416
+ "While parsing a string missing the left delimiter in object value context, we found a , or } and we couldn't determine that a right delimiter was present. Stopping here",
393
417
  "info",
394
418
  )
395
419
  break
396
- elif self.get_context() == "object_value" and char in [",", "}"]:
397
- rstring_delimiter_missing = True
398
- # check if this is a case in which the closing comma is NOT missing instead
399
- i = 1
400
- next_c = self.get_char_at(i)
401
- while next_c and next_c != rstring_delimiter:
402
- i += 1
403
- next_c = self.get_char_at(i)
404
- if next_c:
405
- i += 1
406
- next_c = self.get_char_at(i)
407
- # found a delimiter, now we need to check that is followed strictly by a comma or brace
408
- while next_c and next_c.isspace():
409
- i += 1
410
- next_c = self.get_char_at(i)
411
- if next_c and next_c in [",", "}"]:
412
- rstring_delimiter_missing = False
413
- if rstring_delimiter_missing:
414
- self.log(
415
- "While parsing a string missing the left delimiter in object value context, we found a , or } and we couldn't determine that a right delimiter was present. Stopping here",
416
- "info",
417
- )
418
- break
419
420
  string_acc += char
420
421
  self.index += 1
421
422
  char = self.get_char_at()
@@ -507,7 +508,7 @@ class JSONParser:
507
508
  if next_c == "}":
508
509
  # OK this is valid then
509
510
  self.log(
510
- "While parsing a string, we a misplaced quote that would have closed the string but has a different meaning here since this is the last element of the object, ignoring it",
511
+ "While parsing a string, we misplaced a quote that would have closed the string but has a different meaning here since this is the last element of the object, ignoring it",
511
512
  "info",
512
513
  )
513
514
  string_acc += str(char)
@@ -760,7 +761,7 @@ def from_file(
760
761
  return jsonobj
761
762
 
762
763
 
763
- def cli(): # pragma: no cover
764
+ def cli(inline_args: Optional[List[str]] = None) -> int:
764
765
  parser = argparse.ArgumentParser(description="Repair and parse JSON files.")
765
766
  parser.add_argument("filename", help="The JSON file to repair")
766
767
  parser.add_argument(
@@ -769,10 +770,16 @@ def cli(): # pragma: no cover
769
770
  action="store_true",
770
771
  help="Replace the file inline instead of returning the output to stdout",
771
772
  )
773
+ parser.add_argument(
774
+ "-o",
775
+ "--output",
776
+ metavar="TARGET",
777
+ help="If specified, the output will be written to TARGET filename instead of stdout",
778
+ )
772
779
  parser.add_argument(
773
780
  "--ensure_ascii",
774
781
  action="store_true",
775
- help="Pass the ensure_ascii parameter to json.dumps()",
782
+ help="Pass ensure_ascii=True to json.dumps()",
776
783
  )
777
784
  parser.add_argument(
778
785
  "--indent",
@@ -781,24 +788,36 @@ def cli(): # pragma: no cover
781
788
  help="Number of spaces for indentation (Default 2)",
782
789
  )
783
790
 
784
- args = parser.parse_args()
791
+ if inline_args is None: # pragma: no cover
792
+ args = parser.parse_args()
793
+ else:
794
+ args = parser.parse_args(
795
+ inline_args
796
+ ) # This is needed so this function is testable
797
+
798
+ if args.inline and args.output: # pragma: no cover
799
+ print("Error: You cannot pass both --inline and --output", file=sys.stderr)
800
+ sys.exit(1)
785
801
 
786
802
  ensure_ascii = False
787
803
  if args.ensure_ascii:
788
804
  ensure_ascii = True
805
+
789
806
  try:
790
807
  result = from_file(args.filename)
791
808
 
792
- if args.inline:
793
- fd = open(args.filename, mode="w")
809
+ if args.inline or args.output:
810
+ fd = open(args.output or args.filename, mode="w")
794
811
  json.dump(result, fd, indent=args.indent, ensure_ascii=ensure_ascii)
795
812
  fd.close()
796
813
  else:
797
814
  print(json.dumps(result, indent=args.indent, ensure_ascii=ensure_ascii))
798
- except Exception as e:
815
+ except Exception as e: # pragma: no cover
799
816
  print(f"Error: {str(e)}", file=sys.stderr)
800
817
  sys.exit(1)
801
818
 
819
+ return 0 # Success
820
+
802
821
 
803
822
  if __name__ == "__main__": # pragma: no cover
804
- cli()
823
+ sys.exit(cli())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: json_repair
3
- Version: 0.29.0
3
+ Version: 0.29.2
4
4
  Summary: A package to repair broken json strings
5
5
  Author-email: Stefano Baccianella <4247706+mangiucugna@users.noreply.github.com>
6
6
  License: MIT License
@@ -156,24 +156,24 @@ Install the library for command-line with:
156
156
  ```
157
157
  pipx install json-repair
158
158
  ```
159
- then run
159
+ to know all options available:
160
160
  ```
161
161
  $ json_repair -h
162
-
163
- usage: json_repair [-h] [-i] [--ensure_ascii] [--indent INDENT] filename
162
+ usage: json_repair [-h] [-i] [-o TARGET] [--ensure_ascii] [--indent INDENT] filename
164
163
 
165
164
  Repair and parse JSON files.
166
165
 
167
166
  positional arguments:
168
- filename The JSON file to repair
167
+ filename The JSON file to repair
169
168
 
170
169
  options:
171
- -h, --help show this help message and exit
172
- -i, --inline Replace the file inline instead of returning the output to stdout
173
- --ensure_ascii Pass the ensure_ascii parameter to json.dumps()
174
- --indent INDENT Number of spaces for indentation (Default 2)
170
+ -h, --help show this help message and exit
171
+ -i, --inline Replace the file inline instead of returning the output to stdout
172
+ -o TARGET, --output TARGET
173
+ If specified, the output will be written to TARGET filename instead of stdout
174
+ --ensure_ascii Pass ensure_ascii=True to json.dumps()
175
+ --indent INDENT Number of spaces for indentation (Default 2)
175
176
  ```
176
- to learn how to use it
177
177
 
178
178
  ## Adding to requirements
179
179
  **Please pin this library only on the major version!**
@@ -1,4 +1,8 @@
1
- from src.json_repair.json_repair import from_file, repair_json, loads
1
+ from src.json_repair.json_repair import from_file, repair_json, loads, cli
2
+ from unittest.mock import patch
3
+ import os.path
4
+ import pathlib
5
+ import tempfile
2
6
 
3
7
  def test_basic_types_valid():
4
8
  assert repair_json("True", return_objects=True) == ""
@@ -94,6 +98,8 @@ def test_missing_and_mixed_quotes():
94
98
  repair_json('{"name": "John", "age": 30, "city": "New')
95
99
  == '{"name": "John", "age": 30, "city": "New"}'
96
100
  )
101
+ assert repair_json('{"name": "John", "age": 30, "city": "New York, "gender": "male"}') == '{"name": "John", "age": 30, "city": "New York", "gender": "male"}'
102
+
97
103
  assert repair_json('[{"key": "value", COMMENT "notes": "lorem "ipsum", sic." }]') == '[{"key": "value", "notes": "lorem \\"ipsum\\", sic."}]'
98
104
  assert repair_json('{"key": ""value"}') == '{"key": "value"}'
99
105
  assert repair_json('{"key": "value", 5: "value"}') == '{"key": "value", "5": "value"}'
@@ -225,10 +231,6 @@ def test_repair_json_skip_json_loads():
225
231
 
226
232
 
227
233
  def test_repair_json_from_file():
228
- import os.path
229
- import pathlib
230
- import tempfile
231
-
232
234
  path = pathlib.Path(__file__).parent.resolve()
233
235
 
234
236
  # Use chunk_length 2 to test the buffering feature
@@ -263,3 +265,50 @@ def test_repair_json_from_file():
263
265
 
264
266
  def test_ensure_ascii():
265
267
  assert repair_json("{'test_中国人_ascii':'统一码'}", ensure_ascii=False) == '{"test_中国人_ascii": "统一码"}'
268
+
269
+
270
+
271
+ def test_cli(capsys):
272
+ # Create a temporary file
273
+ temp_fd, temp_path = tempfile.mkstemp(suffix=".json")
274
+ try:
275
+ # Write content to the temporary file
276
+ with os.fdopen(temp_fd, 'w') as tmp:
277
+ tmp.write("{key:value")
278
+ cli(inline_args=[temp_path, '--indent', 0, '--ensure_ascii'])
279
+ captured = capsys.readouterr()
280
+ assert captured.out == '{\n"key": "value"\n}\n'
281
+
282
+ # Test the output option
283
+ tempout_fd, tempout_path = tempfile.mkstemp(suffix=".json")
284
+ cli(inline_args=[temp_path, '--indent', 0, '-o', tempout_path])
285
+ with open(tempout_path, 'r') as tmp:
286
+ out = tmp.read()
287
+ assert out == '{\n"key": "value"\n}'
288
+
289
+ # Test the inline option
290
+ cli(inline_args=[temp_path, '--indent', 0, '-i'])
291
+ with open(temp_path, 'r') as tmp:
292
+ out = tmp.read()
293
+ assert out == '{\n"key": "value"\n}'
294
+
295
+
296
+ finally:
297
+ # Clean up - delete the temporary file
298
+ os.remove(temp_path)
299
+ os.remove(tempout_path)
300
+
301
+ """
302
+ def test_cli_inline(sample_json_file):
303
+ with patch('sys.argv', ['json_repair', sample_json_file, '-i']):
304
+ cli()
305
+ with open(sample_json_file, 'r') as f:
306
+ assert json.load(f) == {"key": "value"}
307
+
308
+ def test_cli_output_file(sample_json_file, tmp_path):
309
+ output_file = tmp_path / "output.json"
310
+ with patch('sys.argv', ['json_repair', sample_json_file, '-o', str(output_file)]):
311
+ cli()
312
+ with open(output_file, 'r') as f:
313
+ assert json.load(f) == {"key": "value"}
314
+ """
@@ -19,7 +19,7 @@ def test_true_true_correct(benchmark):
19
19
  mean_time = benchmark.stats.get("median")
20
20
 
21
21
  # Define your time threshold in seconds
22
- max_time = 15 / 10 ** 4 # 1.5 millisecond
22
+ max_time = 2 / 10 ** 3 # 2 millisecond
23
23
 
24
24
  # Assert that the average time is below the threshold
25
25
  assert mean_time < max_time, f"Benchmark exceeded threshold: {mean_time:.3f}s > {max_time:.3f}s"
@@ -31,7 +31,7 @@ def test_true_true_incorrect(benchmark):
31
31
  mean_time = benchmark.stats.get("median")
32
32
 
33
33
  # Define your time threshold in seconds
34
- max_time = 15 / 10 ** 4 # 1.5 millisecond
34
+ max_time = 2 / 10 ** 3 # 2 millisecond
35
35
 
36
36
  # Assert that the average time is below the threshold
37
37
  assert mean_time < max_time, f"Benchmark exceeded threshold: {mean_time:.3f}s > {max_time:.3f}s"
@@ -53,7 +53,7 @@ def test_true_false_incorrect(benchmark):
53
53
  mean_time = benchmark.stats.get("median")
54
54
 
55
55
  # Define your time threshold in seconds
56
- max_time = 15 / 10 ** 4 # 1.5 millisecond
56
+ max_time = 2 / 10 ** 3 # 2 millisecond
57
57
 
58
58
  # Assert that the average time is below the threshold
59
59
  assert mean_time < max_time, f"Benchmark exceeded threshold: {mean_time:.3f}s > {max_time:.3f}s"
@@ -64,7 +64,7 @@ def test_false_true_correct(benchmark):
64
64
  mean_time = benchmark.stats.get("median")
65
65
 
66
66
  # Define your time threshold in seconds
67
- max_time = 15 / 10 ** 4 # 1.5 millisecond
67
+ max_time = 2 / 10 ** 3 # 2 millisecond
68
68
 
69
69
  # Assert that the average time is below the threshold
70
70
  assert mean_time < max_time, f"Benchmark exceeded threshold: {mean_time:.3f}s > {max_time:.3f}s"
@@ -75,7 +75,7 @@ def test_false_true_incorrect(benchmark):
75
75
  mean_time = benchmark.stats.get("median")
76
76
 
77
77
  # Define your time threshold in seconds
78
- max_time = 15 / 10 ** 4 # 1.5 millisecond
78
+ max_time = 2 / 10 ** 3 # 2 millisecond
79
79
 
80
80
  # Assert that the average time is below the threshold
81
81
  assert mean_time < max_time, f"Benchmark exceeded threshold: {mean_time:.3f}s > {max_time:.3f}s"
@@ -97,7 +97,7 @@ def test_false_false_incorrect(benchmark):
97
97
  mean_time = benchmark.stats.get("median")
98
98
 
99
99
  # Define your time threshold in seconds
100
- max_time = 15 / 10 ** 4 # 1.5 millisecond
100
+ max_time = 2 / 10 ** 3 # 2 millisecond
101
101
 
102
102
  # Assert that the average time is below the threshold
103
103
  assert mean_time < max_time, f"Benchmark exceeded threshold: {mean_time:.3f}s > {max_time:.3f}s"
File without changes
File without changes