json-repair 0.44.0__py3-none-any.whl → 0.45.0__py3-none-any.whl

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.
@@ -1,5 +1,4 @@
1
1
  from enum import Enum, auto
2
- from typing import List, Optional
3
2
 
4
3
 
5
4
  class ContextValues(Enum):
@@ -10,8 +9,8 @@ class ContextValues(Enum):
10
9
 
11
10
  class JsonContext:
12
11
  def __init__(self) -> None:
13
- self.context: List[ContextValues] = []
14
- self.current: Optional[ContextValues] = None
12
+ self.context: list[ContextValues] = []
13
+ self.current: ContextValues | None = None
15
14
  self.empty: bool = True
16
15
 
17
16
  def set(self, value: ContextValues) -> None:
@@ -1,26 +1,27 @@
1
- from typing import Any, Dict, List, Literal, Optional, TextIO, Tuple, Union
1
+ from typing import Any, ClassVar, Literal, TextIO
2
2
 
3
3
  from .json_context import ContextValues, JsonContext
4
4
  from .object_comparer import ObjectComparer
5
5
  from .string_file_wrapper import StringFileWrapper
6
6
 
7
- JSONReturnType = Union[Dict[str, Any], List[Any], str, float, int, bool, None]
7
+ JSONReturnType = dict[str, Any] | list[Any] | str | float | int | bool | None
8
8
 
9
9
 
10
10
  class JSONParser:
11
11
  # Constants
12
- STRING_DELIMITERS = ['"', "'", "“", "”"]
12
+ STRING_DELIMITERS: ClassVar[list[str]] = ['"', "'", "“", "”"]
13
+ NUMBER_CHARS: ClassVar[set[str]] = set("0123456789-.eE/,")
13
14
 
14
15
  def __init__(
15
16
  self,
16
- json_str: Union[str, StringFileWrapper],
17
- json_fd: Optional[TextIO],
18
- logging: Optional[bool],
17
+ json_str: str | StringFileWrapper,
18
+ json_fd: TextIO | None,
19
+ logging: bool | None,
19
20
  json_fd_chunk_length: int = 0,
20
21
  stream_stable: bool = False,
21
22
  ) -> None:
22
23
  # The string to parse
23
- self.json_str: Union[str, StringFileWrapper] = json_str
24
+ self.json_str: str | StringFileWrapper = json_str
24
25
  # Alternatively, the file description with a json file in it
25
26
  if json_fd:
26
27
  # This is a trick we do to treat the file wrapper as an array
@@ -31,12 +32,12 @@ class JSONParser:
31
32
  self.context = JsonContext()
32
33
  # Use this to log the activity, but only if logging is active
33
34
 
34
- # This is a trick but a beatiful one. We call self.log in the code over and over even if it's not needed.
35
+ # This is a trick but a beautiful one. We call self.log in the code over and over even if it's not needed.
35
36
  # We could add a guard in the code for each call but that would make this code unreadable, so here's this neat trick
36
37
  # Replace self.log with a noop
37
38
  self.logging = logging
38
39
  if logging:
39
- self.logger: List[Dict[str, str]] = []
40
+ self.logger: list[dict[str, str]] = []
40
41
  self.log = self._log
41
42
  else:
42
43
  # No-op
@@ -52,14 +53,13 @@ class JSONParser:
52
53
 
53
54
  def parse(
54
55
  self,
55
- ) -> Union[JSONReturnType, Tuple[JSONReturnType, List[Dict[str, str]]]]:
56
+ ) -> JSONReturnType | tuple[JSONReturnType, list[dict[str, str]]]:
56
57
  json = self.parse_json()
57
58
  if self.index < len(self.json_str):
58
59
  self.log(
59
60
  "The parser returned early, checking if there's more json elements",
60
61
  )
61
62
  json = [json]
62
- last_index = self.index
63
63
  while self.index < len(self.json_str):
64
64
  j = self.parse_json()
65
65
  if j != "":
@@ -67,9 +67,6 @@ class JSONParser:
67
67
  # replace the last entry with the new one since the new one seems an update
68
68
  json.pop()
69
69
  json.append(j)
70
- if self.index == last_index:
71
- self.index += 1
72
- last_index = self.index
73
70
  # If nothing extra was found, don't return an array
74
71
  if len(json) == 1:
75
72
  self.log(
@@ -120,9 +117,9 @@ class JSONParser:
120
117
  else:
121
118
  self.index += 1
122
119
 
123
- def parse_object(self) -> Dict[str, JSONReturnType]:
120
+ def parse_object(self) -> dict[str, JSONReturnType]:
124
121
  # <object> ::= '{' [ <member> *(', ' <member>) ] '}' ; A sequence of 'members'
125
- obj: Dict[str, JSONReturnType] = {}
122
+ obj: dict[str, JSONReturnType] = {}
126
123
  # Stop when you either find the closing parentheses or you have iterated over the entire string
127
124
  while (self.get_char_at() or "}") != "}":
128
125
  # This is what we expect to find:
@@ -173,8 +170,6 @@ class JSONParser:
173
170
  self.index += 1
174
171
  self.skip_whitespaces_at()
175
172
  continue
176
- else:
177
- self.index = rollback_index
178
173
  key = str(self.parse_string())
179
174
  if key == "":
180
175
  self.skip_whitespaces_at()
@@ -228,7 +223,7 @@ class JSONParser:
228
223
  self.index += 1
229
224
  return obj
230
225
 
231
- def parse_array(self) -> List[JSONReturnType]:
226
+ def parse_array(self) -> list[JSONReturnType]:
232
227
  # <array> ::= '[' [ <json> *(', ' <json>) ] ']' ; A sequence of JSON values separated by commas
233
228
  arr = []
234
229
  self.context.set(ContextValues.ARRAY)
@@ -265,7 +260,7 @@ class JSONParser:
265
260
  self.context.reset()
266
261
  return arr
267
262
 
268
- def parse_string(self) -> Union[str, bool, None]:
263
+ def parse_string(self) -> str | bool | None:
269
264
  # <string> is a string of valid characters enclosed in quotes
270
265
  # i.e. { name: "John" }
271
266
  # Somehow all weird cases in an invalid JSON happen to be resolved in this function, so be careful here
@@ -312,59 +307,50 @@ class JSONParser:
312
307
  self.index += 1
313
308
 
314
309
  # There is sometimes a weird case of doubled quotes, we manage this also later in the while loop
315
- if self.get_char_at() in self.STRING_DELIMITERS:
316
- # If the next character is the same type of quote, then we manage it as double quotes
317
- if self.get_char_at() == lstring_delimiter:
318
- # If it's an empty key, this was easy
319
- if (
320
- self.context.current == ContextValues.OBJECT_KEY
321
- and self.get_char_at(1) == ":"
322
- ):
323
- self.index += 1
324
- return ""
325
- if self.get_char_at(1) == lstring_delimiter:
326
- # There's something fishy about this, we found doubled quotes and then again quotes
327
- self.log(
328
- "While parsing a string, we found a doubled quote and then a quote again, ignoring it",
329
- )
330
- return ""
331
- # Find the next delimiter
332
- i = self.skip_to_character(character=rstring_delimiter, idx=1)
310
+ if (
311
+ self.get_char_at() in self.STRING_DELIMITERS
312
+ and self.get_char_at() == lstring_delimiter
313
+ ):
314
+ # If it's an empty key, this was easy
315
+ if (
316
+ self.context.current == ContextValues.OBJECT_KEY
317
+ and self.get_char_at(1) == ":"
318
+ ):
319
+ self.index += 1
320
+ return ""
321
+ if self.get_char_at(1) == lstring_delimiter:
322
+ # There's something fishy about this, we found doubled quotes and then again quotes
323
+ self.log(
324
+ "While parsing a string, we found a doubled quote and then a quote again, ignoring it",
325
+ )
326
+ return ""
327
+ # Find the next delimiter
328
+ i = self.skip_to_character(character=rstring_delimiter, idx=1)
329
+ next_c = self.get_char_at(i)
330
+ # Now check that the next character is also a delimiter to ensure that we have "".....""
331
+ # In that case we ignore this rstring delimiter
332
+ if next_c and (self.get_char_at(i + 1) or "") == rstring_delimiter:
333
+ self.log(
334
+ "While parsing a string, we found a valid starting doubled quote",
335
+ )
336
+ doubled_quotes = True
337
+ self.index += 1
338
+ else:
339
+ # Ok this is not a doubled quote, check if this is an empty string or not
340
+ i = self.skip_whitespaces_at(idx=1, move_main_index=False)
333
341
  next_c = self.get_char_at(i)
334
- # Now check that the next character is also a delimiter to ensure that we have "".....""
335
- # In that case we ignore this rstring delimiter
336
- if next_c and (self.get_char_at(i + 1) or "") == rstring_delimiter:
342
+ if next_c in self.STRING_DELIMITERS + ["{", "["]:
343
+ # something fishy is going on here
337
344
  self.log(
338
- "While parsing a string, we found a valid starting doubled quote",
345
+ "While parsing a string, we found a doubled quote but also another quote afterwards, ignoring it",
339
346
  )
340
- doubled_quotes = True
341
347
  self.index += 1
342
- else:
343
- # Ok this is not a doubled quote, check if this is an empty string or not
344
- i = self.skip_whitespaces_at(idx=1, move_main_index=False)
345
- next_c = self.get_char_at(i)
346
- if next_c in self.STRING_DELIMITERS + ["{", "["]:
347
- # something fishy is going on here
348
- self.log(
349
- "While parsing a string, we found a doubled quote but also another quote afterwards, ignoring it",
350
- )
351
- self.index += 1
352
- return ""
353
- elif next_c not in [",", "]", "}"]:
354
- self.log(
355
- "While parsing a string, we found a doubled quote but it was a mistake, removing one quote",
356
- )
357
- self.index += 1
358
- else:
359
- # Otherwise we need to do another check before continuing
360
- i = self.skip_to_character(character=rstring_delimiter, idx=1)
361
- next_c = self.get_char_at(i)
362
- if not next_c:
363
- # mmmm that delimiter never appears again, this is a mistake
348
+ return ""
349
+ elif next_c not in [",", "]", "}"]:
364
350
  self.log(
365
- "While parsing a string, we found a quote but it was a mistake, ignoring it",
351
+ "While parsing a string, we found a doubled quote but it was a mistake, removing one quote",
366
352
  )
367
- return ""
353
+ self.index += 1
368
354
 
369
355
  # Initialize our return value
370
356
  string_acc = ""
@@ -413,10 +399,6 @@ class JSONParser:
413
399
  # So we need to check if we find a new lstring_delimiter afterwards
414
400
  # If we do, maybe this is a missing delimiter
415
401
  i = self.skip_to_character(character=lstring_delimiter, idx=i)
416
- if doubled_quotes:
417
- i = self.skip_to_character(
418
- character=lstring_delimiter, idx=i
419
- )
420
402
  next_c = self.get_char_at(i)
421
403
  if not next_c:
422
404
  rstring_delimiter_missing = False
@@ -457,8 +439,6 @@ class JSONParser:
457
439
  # Ok then this is part of the string
458
440
  rstring_delimiter_missing = False
459
441
  break
460
- elif c == "}":
461
- break
462
442
  if rstring_delimiter_missing:
463
443
  self.log(
464
444
  "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",
@@ -610,15 +590,6 @@ class JSONParser:
610
590
  i += 1
611
591
  i = self.skip_whitespaces_at(idx=i, move_main_index=False)
612
592
  next_c = self.get_char_at(i)
613
- if next_c == "}":
614
- # OK this is valid then
615
- self.log(
616
- "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",
617
- )
618
- unmatched_delimiter = not unmatched_delimiter
619
- string_acc += str(char)
620
- self.index += 1
621
- char = self.get_char_at()
622
593
  elif (
623
594
  next_c == rstring_delimiter and self.get_char_at(i - 1) != "\\"
624
595
  ):
@@ -707,13 +678,12 @@ class JSONParser:
707
678
 
708
679
  return string_acc
709
680
 
710
- def parse_number(self) -> Union[float, int, str, JSONReturnType]:
681
+ def parse_number(self) -> float | int | str | JSONReturnType:
711
682
  # <number> is a valid real number expressed in one of a number of given formats
712
683
  number_str = ""
713
684
  char = self.get_char_at()
714
685
  is_array = self.context.current == ContextValues.ARRAY
715
- NUMBER_CHARS = set("0123456789-.eE/,")
716
- while char and char in NUMBER_CHARS and (not is_array or char != ","):
686
+ while char and char in self.NUMBER_CHARS and (not is_array or char != ","):
717
687
  number_str += char
718
688
  self.index += 1
719
689
  char = self.get_char_at()
@@ -730,19 +700,16 @@ class JSONParser:
730
700
  return str(number_str)
731
701
  if "." in number_str or "e" in number_str or "E" in number_str:
732
702
  return float(number_str)
733
- elif number_str == "-":
734
- # If there is a stray "-" this will throw an exception, throw away this character
735
- return self.parse_json()
736
703
  else:
737
704
  return int(number_str)
738
705
  except ValueError:
739
706
  return number_str
740
707
 
741
- def parse_boolean_or_null(self) -> Union[bool, str, None]:
708
+ def parse_boolean_or_null(self) -> bool | str | None:
742
709
  # <boolean> is one of the literal strings 'true', 'false', or 'null' (unquoted)
743
710
  starting_index = self.index
744
711
  char = (self.get_char_at() or "").lower()
745
- value: Optional[Tuple[str, Optional[bool]]]
712
+ value: tuple[str, bool | None] | None = None
746
713
  if char == "t":
747
714
  value = ("true", True)
748
715
  elif char == "f":
@@ -823,17 +790,9 @@ class JSONParser:
823
790
  break
824
791
  self.log(f"Found block comment: {comment}")
825
792
  return ""
826
- else:
827
- # Not a recognized comment pattern, skip the slash.
828
- self.index += 1
829
- return ""
830
-
831
- else:
832
- # Should not be reached: if for some reason the current character does not start a comment, skip it.
833
- self.index += 1
834
- return ""
793
+ return "" # pragma: no cover
835
794
 
836
- def get_char_at(self, count: int = 0) -> Union[str, Literal[False]]:
795
+ def get_char_at(self, count: int = 0) -> str | Literal[False]:
837
796
  # Why not use something simpler? Because try/except in python is a faster alternative to an "if" statement that is often True
838
797
  try:
839
798
  return self.json_str[self.index + count]
@@ -873,9 +832,6 @@ class JSONParser:
873
832
  char = self.json_str[self.index + idx]
874
833
  except IndexError:
875
834
  return idx
876
- if self.index + idx > 0 and self.json_str[self.index + idx - 1] == "\\":
877
- # Ah this is an escaped character, try again
878
- return self.skip_to_character(character=character, idx=idx + 1)
879
835
  return idx
880
836
 
881
837
  def _log(self, text: str) -> None:
@@ -14,7 +14,7 @@ This module will parse the JSON file following the BNF definition:
14
14
  <object> ::= '{' [ <member> *(', ' <member>) ] '}' ; A sequence of 'members'
15
15
  <member> ::= <string> ': ' <json> ; A pair consisting of a name, and a JSON value
16
16
 
17
- If something is wrong (a missing parantheses or quotes for example) it will use a few simple heuristics to fix the JSON string:
17
+ If something is wrong (a missing parentheses or quotes for example) it will use a few simple heuristics to fix the JSON string:
18
18
  - Add the missing parentheses if the parser believes that the array or object should be closed
19
19
  - Quote strings or add missing single quotes
20
20
  - Adjust whitespaces and remove line breaks
@@ -25,21 +25,47 @@ All supported use cases are in the unit tests
25
25
  import argparse
26
26
  import json
27
27
  import sys
28
- from typing import Dict, List, Optional, TextIO, Tuple, Union
28
+ from typing import Literal, TextIO, overload
29
29
 
30
30
  from .json_parser import JSONParser, JSONReturnType
31
31
 
32
32
 
33
+ @overload
34
+ def repair_json(
35
+ json_str: str = "",
36
+ return_objects: Literal[False] = False,
37
+ skip_json_loads: bool = False,
38
+ logging: bool = False,
39
+ json_fd: TextIO | None = None,
40
+ ensure_ascii: bool = True,
41
+ chunk_length: int = 0,
42
+ stream_stable: bool = False,
43
+ ) -> str: ...
44
+
45
+
46
+ @overload
47
+ def repair_json(
48
+ json_str: str = "",
49
+ return_objects: Literal[True] = True,
50
+ skip_json_loads: bool = False,
51
+ logging: bool = False,
52
+ json_fd: TextIO | None = None,
53
+ ensure_ascii: bool = True,
54
+ chunk_length: int = 0,
55
+ stream_stable: bool = False,
56
+ ) -> JSONReturnType | tuple[JSONReturnType, list[dict[str, str]]]: ...
57
+
58
+
33
59
  def repair_json(
34
60
  json_str: str = "",
35
61
  return_objects: bool = False,
36
62
  skip_json_loads: bool = False,
37
63
  logging: bool = False,
38
- json_fd: Optional[TextIO] = None,
64
+ json_fd: TextIO | None = None,
39
65
  ensure_ascii: bool = True,
40
66
  chunk_length: int = 0,
41
67
  stream_stable: bool = False,
42
- ) -> Union[JSONReturnType, Tuple[JSONReturnType, List[Dict[str, str]]]]:
68
+ ) -> JSONReturnType | tuple[JSONReturnType, list[dict[str, str]]]:
43
69
  """
44
70
  Given a json formatted string, it will try to decode it and, if it fails, it will try to fix it.
45
71
 
@@ -60,16 +86,16 @@ def repair_json(
60
86
  parsed_json = parser.parse()
61
87
  else:
62
88
  try:
63
- if json_fd:
64
- parsed_json = json.load(json_fd)
65
- else:
66
- parsed_json = json.loads(json_str)
89
+ parsed_json = json.load(json_fd) if json_fd else json.loads(json_str)
67
90
  except json.JSONDecodeError:
68
91
  parsed_json = parser.parse()
69
92
  # It's useful to return the actual object instead of the json string,
70
93
  # it allows this lib to be a replacement of the json library
71
94
  if return_objects or logging:
72
95
  return parsed_json
96
+ # Avoid returning only a pair of quotes if it's an empty string
97
+ elif parsed_json == "":
98
+ return ""
73
99
  return json.dumps(parsed_json, ensure_ascii=ensure_ascii)
74
100
 
75
101
 
@@ -78,7 +104,7 @@ def loads(
78
104
  skip_json_loads: bool = False,
79
105
  logging: bool = False,
80
106
  stream_stable: bool = False,
81
- ) -> Union[JSONReturnType, Tuple[JSONReturnType, List[Dict[str, str]]]]:
107
+ ) -> JSONReturnType | tuple[JSONReturnType, list[dict[str, str]]] | str:
82
108
  """
83
109
  This function works like `json.loads()` except that it will fix your JSON in the process.
84
110
  It is a wrapper around the `repair_json()` function with `return_objects=True`.
@@ -89,7 +115,7 @@ def loads(
89
115
  logging (bool, optional): If True, return a tuple with the repaired json and a log of all repair actions. Defaults to False.
90
116
 
91
117
  Returns:
92
- Union[JSONReturnType, Tuple[JSONReturnType, List[Dict[str, str]]]]: The repaired JSON object or a tuple with the repaired JSON object and repair log.
118
+ Union[JSONReturnType, Tuple[JSONReturnType, List[Dict[str, str]]], str]: The repaired JSON object or a tuple with the repaired JSON object and repair log.
93
119
  """
94
120
  return repair_json(
95
121
  json_str=json_str,
@@ -105,7 +131,7 @@ def load(
105
131
  skip_json_loads: bool = False,
106
132
  logging: bool = False,
107
133
  chunk_length: int = 0,
108
- ) -> Union[JSONReturnType, Tuple[JSONReturnType, List[Dict[str, str]]]]:
134
+ ) -> JSONReturnType | tuple[JSONReturnType, list[dict[str, str]]]:
109
135
  """
110
136
  This function works like `json.load()` except that it will fix your JSON in the process.
111
137
  It is a wrapper around the `repair_json()` function with `json_fd=fd` and `return_objects=True`.
@@ -133,7 +159,7 @@ def from_file(
133
159
  skip_json_loads: bool = False,
134
160
  logging: bool = False,
135
161
  chunk_length: int = 0,
136
- ) -> Union[JSONReturnType, Tuple[JSONReturnType, List[Dict[str, str]]]]:
162
+ ) -> JSONReturnType | tuple[JSONReturnType, list[dict[str, str]]]:
137
163
  """
138
164
  This function is a wrapper around `load()` so you can pass the filename as string
139
165
 
@@ -157,7 +183,7 @@ def from_file(
157
183
  return jsonobj
158
184
 
159
185
 
160
- def cli(inline_args: Optional[List[str]] = None) -> int:
186
+ def cli(inline_args: list[str] | None = None) -> int:
161
187
  """
162
188
  Command-line interface for repairing and parsing JSON files.
163
189
 
@@ -1,9 +1,9 @@
1
1
  from typing import Any
2
2
 
3
3
 
4
- class ObjectComparer:
4
+ class ObjectComparer: # pragma: no cover
5
5
  def __init__(self) -> None:
6
- return
6
+ pass # No operation performed in the constructor
7
7
 
8
8
  @staticmethod
9
9
  def is_same_object(obj1: Any, obj2: Any, path: str = "") -> bool:
@@ -16,40 +16,24 @@ class ObjectComparer:
16
16
  # Fail immediately if the types don't match
17
17
  return False
18
18
 
19
- if isinstance(obj1, dict) and isinstance(obj2, dict):
20
- # Compare dictionary keys
21
- keys1, keys2 = set(obj1.keys()), set(obj2.keys())
22
- common_keys = keys1 & keys2
23
- extra_keys1 = keys1 - keys2
24
- extra_keys2 = keys2 - keys1
25
-
26
- if extra_keys1:
27
- return False
28
- if extra_keys2:
19
+ if isinstance(obj1, dict):
20
+ # Quick length check before key compares
21
+ if len(obj1) != len(obj2):
29
22
  return False
30
-
31
- # Recursively compare the common keys
32
- for key in common_keys:
33
- if not ObjectComparer.is_same_object(
34
- obj1[key], obj2[key], path=f"{path}/{key}"
35
- ):
23
+ for key in obj1:
24
+ if key not in obj2:
25
+ return False
26
+ if not ObjectComparer.is_same_object(obj1[key], obj2[key]):
36
27
  return False
28
+ return True
37
29
 
38
- elif isinstance(obj1, list) and isinstance(obj2, list):
39
- # Compare lists
40
- min_length = min(len(obj1), len(obj2))
30
+ elif isinstance(obj1, list):
41
31
  if len(obj1) != len(obj2):
42
32
  return False
43
-
44
- for i in range(min_length):
45
- if not ObjectComparer.is_same_object(
46
- obj1[i], obj2[i], path=f"{path}[{i}]"
47
- ):
33
+ for i in range(len(obj1)):
34
+ if not ObjectComparer.is_same_object(obj1[i], obj2[i]):
48
35
  return False
36
+ return True
49
37
 
50
- if len(obj1) > len(obj2):
51
- return False
52
- elif len(obj2) > len(obj1):
53
- return False
54
-
38
+ # For atoms: types already match, so just return True
55
39
  return True
@@ -1,5 +1,5 @@
1
1
  import os
2
- from typing import TextIO, Union
2
+ from typing import TextIO
3
3
 
4
4
 
5
5
  class StringFileWrapper:
@@ -48,7 +48,7 @@ class StringFileWrapper:
48
48
  self.buffers.pop(oldest_key)
49
49
  return self.buffers[index]
50
50
 
51
- def __getitem__(self, index: Union[int, slice]) -> str:
51
+ def __getitem__(self, index: int | slice) -> str:
52
52
  """
53
53
  Retrieve a character or a slice of characters from the file.
54
54
 
@@ -97,7 +97,7 @@ class StringFileWrapper:
97
97
  self.fd.seek(current_position)
98
98
  return self.length
99
99
 
100
- def __setitem__(self, index: Union[int, slice], value: str) -> None:
100
+ def __setitem__(self, index: int | slice, value: str) -> None: # pragma: no cover
101
101
  """
102
102
  Set a character or a slice of characters in the file.
103
103
 
@@ -105,10 +105,7 @@ class StringFileWrapper:
105
105
  index (slice): The slice of characters to set.
106
106
  value (str): The value to set at the specified index or slice.
107
107
  """
108
- if isinstance(index, slice):
109
- start = index.start or 0
110
- else:
111
- start = index or 0
108
+ start = index.start or 0 if isinstance(index, slice) else index or 0
112
109
 
113
110
  if start < 0:
114
111
  start += len(self)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: json_repair
3
- Version: 0.44.0
3
+ Version: 0.45.0
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
@@ -0,0 +1,14 @@
1
+ json_repair/__init__.py,sha256=c4L2kZrHvWEKfj_ODU2naliNuvU6FlFVxtF0hbLe6s8,178
2
+ json_repair/__main__.py,sha256=EsJb-y89uZEvGQQg1GdIDWzfDwfOMvVekKEtdguQXCM,67
3
+ json_repair/json_context.py,sha256=WsMOjqpGSr6aaDONcrk8UFtTurzWon2Qq9AoBBYseoI,934
4
+ json_repair/json_parser.py,sha256=DnLeC0SKTs9yaixYBYZ3J4gwxGXwOLsTWHelptGUp4Q,39407
5
+ json_repair/json_repair.py,sha256=9wxf0vVNfr_RNQI1rbVPvxQ9feEwwvgnvkiYXwGEBX8,11292
6
+ json_repair/object_comparer.py,sha256=ZjxrzepSNGrhiwzid2Dm657x1Aj-E1-h37bDygK8ByE,1261
7
+ json_repair/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ json_repair/string_file_wrapper.py,sha256=uwW4B1s9Cf-iF3ANsCz-RPu2ddCqDETrt8bdojh8ufA,4485
9
+ json_repair-0.45.0.dist-info/licenses/LICENSE,sha256=wrjQo8MhNrNCicXtMe3MHmS-fx8AmQk1ue8AQwiiFV8,1076
10
+ json_repair-0.45.0.dist-info/METADATA,sha256=0RAn-AntRZtUdgJcBt8tvsZG-hHadaGfB9Vy2PBRcRk,12157
11
+ json_repair-0.45.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
12
+ json_repair-0.45.0.dist-info/entry_points.txt,sha256=SNfge3zPSP-ASqriYU9r3NAPaXdseYr7ciPMKdV2uSw,57
13
+ json_repair-0.45.0.dist-info/top_level.txt,sha256=7-VZwZN2CgB_n0NlSLk-rEUFh8ug21lESbsblOYuZqw,12
14
+ json_repair-0.45.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.0.0)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,14 +0,0 @@
1
- json_repair/__init__.py,sha256=c4L2kZrHvWEKfj_ODU2naliNuvU6FlFVxtF0hbLe6s8,178
2
- json_repair/__main__.py,sha256=EsJb-y89uZEvGQQg1GdIDWzfDwfOMvVekKEtdguQXCM,67
3
- json_repair/json_context.py,sha256=mm6dOyrPJ1sDskTORZSXCW7W9-5veMlUKqXQ3Hw3EG4,971
4
- json_repair/json_parser.py,sha256=ID60F0RMzaCpeHPkZbuidJcsmrVBiPmQDRUOgjoeedE,41972
5
- json_repair/json_repair.py,sha256=o84um759Alft7mlj7lXZFtPQZQPjbo5Jxraa7dTdiRg,10621
6
- json_repair/object_comparer.py,sha256=SeicB6_N4BHAEPon7s2BELEaJc4oyR9ZhfX2RgPk6Bw,1682
7
- json_repair/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- json_repair/string_file_wrapper.py,sha256=koZmdq2-Z5K7XF1bDqX6dEbNaVMJYcMTjq-aGe6NQvA,4526
9
- json_repair-0.44.0.dist-info/licenses/LICENSE,sha256=wrjQo8MhNrNCicXtMe3MHmS-fx8AmQk1ue8AQwiiFV8,1076
10
- json_repair-0.44.0.dist-info/METADATA,sha256=mu_r9oiyo_35hwk745ZTFoMZrJ9PBjRPjFKgICkKSZQ,12157
11
- json_repair-0.44.0.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
12
- json_repair-0.44.0.dist-info/entry_points.txt,sha256=SNfge3zPSP-ASqriYU9r3NAPaXdseYr7ciPMKdV2uSw,57
13
- json_repair-0.44.0.dist-info/top_level.txt,sha256=7-VZwZN2CgB_n0NlSLk-rEUFh8ug21lESbsblOYuZqw,12
14
- json_repair-0.44.0.dist-info/RECORD,,