json-repair 0.47.4__py3-none-any.whl → 0.47.6__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.
@@ -0,0 +1,413 @@
1
+ from .constants import STRING_DELIMITERS
2
+ from .json_context import ContextValues
3
+
4
+
5
+ def parse_string(self) -> str | bool | None:
6
+ # <string> is a string of valid characters enclosed in quotes
7
+ # i.e. { name: "John" }
8
+ # Somehow all weird cases in an invalid JSON happen to be resolved in this function, so be careful here
9
+
10
+ # Flag to manage corner cases related to missing starting quote
11
+ missing_quotes = False
12
+ doubled_quotes = False
13
+ lstring_delimiter = rstring_delimiter = '"'
14
+
15
+ char = self.get_char_at()
16
+ if char in ["#", "/"]:
17
+ return self.parse_comment()
18
+ # A valid string can only start with a valid quote or, in our case, with a literal
19
+ while char and char not in STRING_DELIMITERS and not char.isalnum():
20
+ self.index += 1
21
+ char = self.get_char_at()
22
+
23
+ if not char:
24
+ # This is an empty string
25
+ return ""
26
+
27
+ # Ensuring we use the right delimiter
28
+ if char == "'":
29
+ lstring_delimiter = rstring_delimiter = "'"
30
+ elif char == "“":
31
+ lstring_delimiter = "“"
32
+ rstring_delimiter = "”"
33
+ elif char.isalnum():
34
+ # This could be a <boolean> and not a string. Because (T)rue or (F)alse or (N)ull are valid
35
+ # But remember, object keys are only of type string
36
+ if char.lower() in ["t", "f", "n"] and self.context.current != ContextValues.OBJECT_KEY:
37
+ value = self.parse_boolean_or_null()
38
+ if value != "":
39
+ return value
40
+ self.log(
41
+ "While parsing a string, we found a literal instead of a quote",
42
+ )
43
+ missing_quotes = True
44
+
45
+ if not missing_quotes:
46
+ self.index += 1
47
+
48
+ # There is sometimes a weird case of doubled quotes, we manage this also later in the while loop
49
+ if self.get_char_at() in STRING_DELIMITERS and self.get_char_at() == lstring_delimiter:
50
+ # If it's an empty key, this was easy
51
+ if (self.context.current == ContextValues.OBJECT_KEY and self.get_char_at(1) == ":") or (
52
+ self.context.current == ContextValues.OBJECT_VALUE and self.get_char_at(1) in [",", "}"]
53
+ ):
54
+ self.index += 1
55
+ return ""
56
+ elif self.get_char_at(1) == lstring_delimiter:
57
+ # There's something fishy about this, we found doubled quotes and then again quotes
58
+ self.log(
59
+ "While parsing a string, we found a doubled quote and then a quote again, ignoring it",
60
+ )
61
+ return ""
62
+ # Find the next delimiter
63
+ i = self.skip_to_character(character=rstring_delimiter, idx=1)
64
+ next_c = self.get_char_at(i)
65
+ # Now check that the next character is also a delimiter to ensure that we have "".....""
66
+ # In that case we ignore this rstring delimiter
67
+ if next_c and (self.get_char_at(i + 1) or "") == rstring_delimiter:
68
+ self.log(
69
+ "While parsing a string, we found a valid starting doubled quote",
70
+ )
71
+ doubled_quotes = True
72
+ self.index += 1
73
+ else:
74
+ # Ok this is not a doubled quote, check if this is an empty string or not
75
+ i = self.skip_whitespaces_at(idx=1, move_main_index=False)
76
+ next_c = self.get_char_at(i)
77
+ if next_c in STRING_DELIMITERS + ["{", "["]:
78
+ # something fishy is going on here
79
+ self.log(
80
+ "While parsing a string, we found a doubled quote but also another quote afterwards, ignoring it",
81
+ )
82
+ self.index += 1
83
+ return ""
84
+ elif next_c not in [",", "]", "}"]:
85
+ self.log(
86
+ "While parsing a string, we found a doubled quote but it was a mistake, removing one quote",
87
+ )
88
+ self.index += 1
89
+
90
+ # Initialize our return value
91
+ string_acc = ""
92
+
93
+ # Here things get a bit hairy because a string missing the final quote can also be a key or a value in an object
94
+ # In that case we need to use the ":|,|}" characters as terminators of the string
95
+ # So this will stop if:
96
+ # * It finds a closing quote
97
+ # * It iterated over the entire sequence
98
+ # * If we are fixing missing quotes in an object, when it finds the special terminators
99
+ char = self.get_char_at()
100
+ unmatched_delimiter = False
101
+ while char and char != rstring_delimiter:
102
+ if missing_quotes and self.context.current == ContextValues.OBJECT_KEY and (char == ":" or char.isspace()):
103
+ self.log(
104
+ "While parsing a string missing the left delimiter in object key context, we found a :, stopping here",
105
+ )
106
+ break
107
+ if (
108
+ not self.stream_stable
109
+ and self.context.current == ContextValues.OBJECT_VALUE
110
+ and char
111
+ in [
112
+ ",",
113
+ "}",
114
+ ]
115
+ and (not string_acc or string_acc[-1] != rstring_delimiter)
116
+ ):
117
+ rstring_delimiter_missing = True
118
+ # check if this is a case in which the closing comma is NOT missing instead
119
+ self.skip_whitespaces_at()
120
+ if self.get_char_at(1) == "\\":
121
+ # Ok this is a quoted string, skip
122
+ rstring_delimiter_missing = False
123
+ i = self.skip_to_character(character=rstring_delimiter, idx=1)
124
+ next_c = self.get_char_at(i)
125
+ if next_c:
126
+ i += 1
127
+ # found a delimiter, now we need to check that is followed strictly by a comma or brace
128
+ # or the string ended
129
+ i = self.skip_whitespaces_at(idx=i, move_main_index=False)
130
+ next_c = self.get_char_at(i)
131
+ if not next_c or next_c in [",", "}"]:
132
+ rstring_delimiter_missing = False
133
+ else:
134
+ # OK but this could still be some garbage at the end of the string
135
+ # So we need to check if we find a new lstring_delimiter afterwards
136
+ # If we do, maybe this is a missing delimiter
137
+ i = self.skip_to_character(character=lstring_delimiter, idx=i)
138
+ next_c = self.get_char_at(i)
139
+ if not next_c:
140
+ rstring_delimiter_missing = False
141
+ else:
142
+ # But again, this could just be something a bit stupid like "lorem, "ipsum" sic"
143
+ # Check if we find a : afterwards (skipping space)
144
+ i = self.skip_whitespaces_at(idx=i + 1, move_main_index=False)
145
+ next_c = self.get_char_at(i)
146
+ if next_c and next_c != ":":
147
+ rstring_delimiter_missing = False
148
+ else:
149
+ # There could be a case in which even the next key:value is missing delimeters
150
+ # because it might be a systemic issue with the output
151
+ # So let's check if we can find a : in the string instead
152
+ i = self.skip_to_character(character=":", idx=1)
153
+ next_c = self.get_char_at(i)
154
+ if next_c:
155
+ # OK then this is a systemic issue with the output
156
+ break
157
+ else:
158
+ # skip any whitespace first
159
+ i = self.skip_whitespaces_at(idx=1, move_main_index=False)
160
+ # We couldn't find any rstring_delimeter before the end of the string
161
+ # check if this is the last string of an object and therefore we can keep going
162
+ # make an exception if this is the last char before the closing brace
163
+ j = self.skip_to_character(character="}", idx=i)
164
+ if j - i > 1:
165
+ # Ok it's not right after the comma
166
+ # Let's ignore
167
+ rstring_delimiter_missing = False
168
+ # Check that j was not out of bound
169
+ elif self.get_char_at(j):
170
+ # Check for an unmatched opening brace in string_acc
171
+ for c in reversed(string_acc):
172
+ if c == "{":
173
+ # Ok then this is part of the string
174
+ rstring_delimiter_missing = False
175
+ break
176
+ if rstring_delimiter_missing:
177
+ self.log(
178
+ "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",
179
+ )
180
+ break
181
+ if (
182
+ not self.stream_stable
183
+ and char == "]"
184
+ and ContextValues.ARRAY in self.context.context
185
+ and string_acc[-1] != rstring_delimiter
186
+ ):
187
+ # We found the end of an array and we are in array context
188
+ # So let's check if we find a rstring_delimiter forward otherwise end early
189
+ i = self.skip_to_character(rstring_delimiter)
190
+ if not self.get_char_at(i):
191
+ # No delimiter found
192
+ break
193
+ string_acc += char
194
+ self.index += 1
195
+ char = self.get_char_at()
196
+ # Unclosed string ends with a \ character. This character is ignored if stream_stable = True.
197
+ if self.stream_stable and not char and string_acc[-1] == "\\":
198
+ string_acc = string_acc[:-1]
199
+ if char and string_acc[-1] == "\\":
200
+ # This is a special case, if people use real strings this might happen
201
+ self.log("Found a stray escape sequence, normalizing it")
202
+ if char in [rstring_delimiter, "t", "n", "r", "b", "\\"]:
203
+ string_acc = string_acc[:-1]
204
+ escape_seqs = {"t": "\t", "n": "\n", "r": "\r", "b": "\b"}
205
+ string_acc += escape_seqs.get(char, char)
206
+ self.index += 1
207
+ char = self.get_char_at()
208
+ while char and string_acc[-1] == "\\" and char in [rstring_delimiter, "\\"]:
209
+ # this is a bit of a special case, if I don't do this it will close the loop or create a train of \\
210
+ # I don't love it though
211
+ string_acc = string_acc[:-1]
212
+ string_acc += char
213
+ self.index += 1
214
+ char = self.get_char_at()
215
+ continue
216
+ elif char in ["u", "x"]:
217
+ # If we find a unicode escape sequence, normalize it
218
+ num_chars = 4 if char == "u" else 2
219
+ next_chars = self.json_str[self.index + 1 : self.index + 1 + num_chars]
220
+ if len(next_chars) == num_chars and all(c in "0123456789abcdefABCDEF" for c in next_chars):
221
+ self.log("Found a unicode escape sequence, normalizing it")
222
+ string_acc = string_acc[:-1]
223
+ string_acc += chr(int(next_chars, 16))
224
+ self.index += 1 + num_chars
225
+ char = self.get_char_at()
226
+ continue
227
+ # If we are in object key context and we find a colon, it could be a missing right quote
228
+ if char == ":" and not missing_quotes and self.context.current == ContextValues.OBJECT_KEY:
229
+ # Ok now we need to check if this is followed by a value like "..."
230
+ i = self.skip_to_character(character=lstring_delimiter, idx=1)
231
+ next_c = self.get_char_at(i)
232
+ if next_c:
233
+ i += 1
234
+ # found the first delimiter
235
+ i = self.skip_to_character(character=rstring_delimiter, idx=i)
236
+ next_c = self.get_char_at(i)
237
+ if next_c:
238
+ # found a second delimiter
239
+ i += 1
240
+ # Skip spaces
241
+ i = self.skip_whitespaces_at(idx=i, move_main_index=False)
242
+ next_c = self.get_char_at(i)
243
+ if next_c and next_c in [",", "}"]:
244
+ # Ok then this is a missing right quote
245
+ self.log(
246
+ "While parsing a string missing the right delimiter in object key context, we found a :, stopping here",
247
+ )
248
+ break
249
+ else:
250
+ # The string ended without finding a lstring_delimiter, I will assume this is a missing right quote
251
+ self.log(
252
+ "While parsing a string missing the right delimiter in object key context, we found a :, stopping here",
253
+ )
254
+ break
255
+ # ChatGPT sometimes forget to quote stuff in html tags or markdown, so we do this whole thing here
256
+ if char == rstring_delimiter and string_acc[-1] != "\\":
257
+ # Special case here, in case of double quotes one after another
258
+ if doubled_quotes and self.get_char_at(1) == rstring_delimiter:
259
+ self.log("While parsing a string, we found a doubled quote, ignoring it")
260
+ self.index += 1
261
+ elif missing_quotes and self.context.current == ContextValues.OBJECT_VALUE:
262
+ # In case of missing starting quote I need to check if the delimeter is the end or the beginning of a key
263
+ i = 1
264
+ next_c = self.get_char_at(i)
265
+ while next_c and next_c not in [
266
+ rstring_delimiter,
267
+ lstring_delimiter,
268
+ ]:
269
+ i += 1
270
+ next_c = self.get_char_at(i)
271
+ if next_c:
272
+ # We found a quote, now let's make sure there's a ":" following
273
+ i += 1
274
+ # found a delimiter, now we need to check that is followed strictly by a comma or brace
275
+ i = self.skip_whitespaces_at(idx=i, move_main_index=False)
276
+ next_c = self.get_char_at(i)
277
+ if next_c and next_c == ":":
278
+ # Reset the cursor
279
+ self.index -= 1
280
+ char = self.get_char_at()
281
+ self.log(
282
+ "In a string with missing quotes and object value context, I found a delimeter but it turns out it was the beginning on the next key. Stopping here.",
283
+ )
284
+ break
285
+ elif unmatched_delimiter:
286
+ unmatched_delimiter = False
287
+ string_acc += str(char)
288
+ self.index += 1
289
+ char = self.get_char_at()
290
+ else:
291
+ # Check if eventually there is a rstring delimiter, otherwise we bail
292
+ i = 1
293
+ next_c = self.get_char_at(i)
294
+ check_comma_in_object_value = True
295
+ while next_c and next_c not in [
296
+ rstring_delimiter,
297
+ lstring_delimiter,
298
+ ]:
299
+ # This is a bit of a weird workaround, essentially in object_value context we don't always break on commas
300
+ # This is because the routine after will make sure to correct any bad guess and this solves a corner case
301
+ if check_comma_in_object_value and next_c.isalpha():
302
+ check_comma_in_object_value = False
303
+ # If we are in an object context, let's check for the right delimiters
304
+ if (
305
+ (ContextValues.OBJECT_KEY in self.context.context and next_c in [":", "}"])
306
+ or (ContextValues.OBJECT_VALUE in self.context.context and next_c == "}")
307
+ or (ContextValues.ARRAY in self.context.context and next_c in ["]", ","])
308
+ or (
309
+ check_comma_in_object_value
310
+ and self.context.current == ContextValues.OBJECT_VALUE
311
+ and next_c == ","
312
+ )
313
+ ):
314
+ break
315
+ i += 1
316
+ next_c = self.get_char_at(i)
317
+ # If we stopped for a comma in object_value context, let's check if find a "} at the end of the string
318
+ if next_c == "," and self.context.current == ContextValues.OBJECT_VALUE:
319
+ i += 1
320
+ i = self.skip_to_character(character=rstring_delimiter, idx=i)
321
+ next_c = self.get_char_at(i)
322
+ # Ok now I found a delimiter, let's skip whitespaces and see if next we find a }
323
+ i += 1
324
+ i = self.skip_whitespaces_at(idx=i, move_main_index=False)
325
+ next_c = self.get_char_at(i)
326
+ elif next_c == rstring_delimiter and self.get_char_at(i - 1) != "\\":
327
+ # Check if self.index:self.index+i is only whitespaces, break if that's the case
328
+ if all(str(self.get_char_at(j)).isspace() for j in range(1, i) if self.get_char_at(j)):
329
+ break
330
+ if self.context.current == ContextValues.OBJECT_VALUE:
331
+ i = self.skip_whitespaces_at(idx=i + 1, move_main_index=False)
332
+ if self.get_char_at(i) == ",":
333
+ # So we found a comma, this could be a case of a single quote like "va"lue",
334
+ # Search if it's followed by another key, starting with the first delimeter
335
+ i = self.skip_to_character(character=lstring_delimiter, idx=i + 1)
336
+ i += 1
337
+ i = self.skip_to_character(character=rstring_delimiter, idx=i + 1)
338
+ i += 1
339
+ i = self.skip_whitespaces_at(idx=i, move_main_index=False)
340
+ next_c = self.get_char_at(i)
341
+ if next_c == ":":
342
+ self.log(
343
+ "While parsing a string, we a misplaced quote that would have closed the string but has a different meaning here, ignoring it",
344
+ )
345
+ string_acc += str(char)
346
+ self.index += 1
347
+ char = self.get_char_at()
348
+ continue
349
+ # We found a delimiter and we need to check if this is a key
350
+ # so find a rstring_delimiter and a colon after
351
+ i = self.skip_to_character(character=rstring_delimiter, idx=i + 1)
352
+ i += 1
353
+ next_c = self.get_char_at(i)
354
+ while next_c and next_c != ":":
355
+ if next_c in [",", "]", "}"] or (
356
+ next_c == rstring_delimiter and self.get_char_at(i - 1) != "\\"
357
+ ):
358
+ break
359
+ i += 1
360
+ next_c = self.get_char_at(i)
361
+ # Only if we fail to find a ':' then we know this is misplaced quote
362
+ if next_c != ":":
363
+ self.log(
364
+ "While parsing a string, we a misplaced quote that would have closed the string but has a different meaning here, ignoring it",
365
+ )
366
+ unmatched_delimiter = not unmatched_delimiter
367
+ string_acc += str(char)
368
+ self.index += 1
369
+ char = self.get_char_at()
370
+ elif self.context.current == ContextValues.ARRAY:
371
+ # If we got up to here it means that this is a situation like this:
372
+ # ["bla bla bla "puppy" bla bla bla "kitty" bla bla"]
373
+ # So we need to ignore this quote
374
+ self.log(
375
+ "While parsing a string in Array context, we detected a quoted section that would have closed the string but has a different meaning here, ignoring it",
376
+ )
377
+ unmatched_delimiter = not unmatched_delimiter
378
+ string_acc += str(char)
379
+ self.index += 1
380
+ char = self.get_char_at()
381
+ elif self.context.current == ContextValues.OBJECT_KEY:
382
+ # In this case we just ignore this and move on
383
+ self.log(
384
+ "While parsing a string in Object Key context, we detected a quoted section that would have closed the string but has a different meaning here, ignoring it",
385
+ )
386
+ string_acc += str(char)
387
+ self.index += 1
388
+ char = self.get_char_at()
389
+ if char and missing_quotes and self.context.current == ContextValues.OBJECT_KEY and char.isspace():
390
+ self.log(
391
+ "While parsing a string, handling an extreme corner case in which the LLM added a comment instead of valid string, invalidate the string and return an empty value",
392
+ )
393
+ self.skip_whitespaces_at()
394
+ if self.get_char_at() not in [":", ","]:
395
+ return ""
396
+
397
+ # A fallout of the previous special case in the while loop,
398
+ # we need to update the index only if we had a closing quote
399
+ if char != rstring_delimiter:
400
+ # if stream_stable = True, unclosed strings do not trim trailing whitespace characters
401
+ if not self.stream_stable:
402
+ self.log(
403
+ "While parsing a string, we missed the closing quote, ignoring",
404
+ )
405
+ string_acc = string_acc.rstrip()
406
+ else:
407
+ self.index += 1
408
+
409
+ if not self.stream_stable and (missing_quotes or (string_acc and string_acc[-1] == "\n")):
410
+ # Clean the whitespaces for some corner cases
411
+ string_acc = string_acc.rstrip()
412
+
413
+ return string_acc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: json_repair
3
- Version: 0.47.4
3
+ Version: 0.47.6
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,21 @@
1
+ json_repair/__init__.py,sha256=JdJIZNCKV3MfIviryqK8NH8yGssCta2-192CekcwH-o,174
2
+ json_repair/__main__.py,sha256=EsJb-y89uZEvGQQg1GdIDWzfDwfOMvVekKEtdguQXCM,67
3
+ json_repair/constants.py,sha256=cv2gvyosuq0me0600WyTysM9avrtfXPuXYR26tawcuo,158
4
+ json_repair/json_context.py,sha256=WsMOjqpGSr6aaDONcrk8UFtTurzWon2Qq9AoBBYseoI,934
5
+ json_repair/json_parser.py,sha256=WpU8K3E51gJ9BKbuW7LcMQGXWArte8noYMzvA9qu6Wc,6850
6
+ json_repair/json_repair.py,sha256=txblCJtcTpXcQaT15tavulkJPtyRYe2cfYpPHZcvPv0,11233
7
+ json_repair/object_comparer.py,sha256=LlIF0MisRglzC-CiG5AxAEDCBWBHeJd-6uXYx0uRmCk,1175
8
+ json_repair/parse_array.py,sha256=YnESGXnRaX57zXv7NP6EcHOlqgeaLEzOy1s_l9ghTeY,2002
9
+ json_repair/parse_boolean_or_null.py,sha256=2KoUkjiZ68fkge_n_Q4bbFVG6WskRroKD55jW2Ep2OU,782
10
+ json_repair/parse_comment.py,sha256=kNTinpdHZftrtV190-Julq5eKFxMSmGYNbwlj4vGtsg,2492
11
+ json_repair/parse_number.py,sha256=o7wEER7_H6xG0WsmvKS8VucoMJ7AsaJdxkDzulJ9o-Q,1192
12
+ json_repair/parse_object.py,sha256=yQ9SilLdBBW5cYJOcQGB4ZR8MtOp4cH6WilfXF_kdgE,4456
13
+ json_repair/parse_string.py,sha256=8f-zr9I2n5tAHkot0pBzqcn8avZbhEb-CX0NLwQUMUU,22131
14
+ json_repair/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ json_repair/string_file_wrapper.py,sha256=tGkWBEUPE-CZPf4uSM5NE9oSDTpskX0myJiXsl-gbds,4333
16
+ json_repair-0.47.6.dist-info/licenses/LICENSE,sha256=wrjQo8MhNrNCicXtMe3MHmS-fx8AmQk1ue8AQwiiFV8,1076
17
+ json_repair-0.47.6.dist-info/METADATA,sha256=S7fZgSot13SzgMXXk4Qh4PJEmDyUJMUy_Vb6FuLW6v8,12411
18
+ json_repair-0.47.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ json_repair-0.47.6.dist-info/entry_points.txt,sha256=SNfge3zPSP-ASqriYU9r3NAPaXdseYr7ciPMKdV2uSw,57
20
+ json_repair-0.47.6.dist-info/top_level.txt,sha256=7-VZwZN2CgB_n0NlSLk-rEUFh8ug21lESbsblOYuZqw,12
21
+ json_repair-0.47.6.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- json_repair/__init__.py,sha256=6FDD6dEVM5Pb5o4Zodgw4ex30Hzy-YvNRy0vts9SQ4I,118
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=412X7sLnSjN3Rk8l0kSSeNbrDn9wrEgHT31Non-qYzU,40567
5
- json_repair/json_repair.py,sha256=0qL2LuzlNJa3VnEqYNaJyZNAL2w18oAt2YvA-TlMxmY,11211
6
- json_repair/object_comparer.py,sha256=LlIF0MisRglzC-CiG5AxAEDCBWBHeJd-6uXYx0uRmCk,1175
7
- json_repair/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- json_repair/string_file_wrapper.py,sha256=tGkWBEUPE-CZPf4uSM5NE9oSDTpskX0myJiXsl-gbds,4333
9
- json_repair-0.47.4.dist-info/licenses/LICENSE,sha256=wrjQo8MhNrNCicXtMe3MHmS-fx8AmQk1ue8AQwiiFV8,1076
10
- json_repair-0.47.4.dist-info/METADATA,sha256=FU5u40Z4wL4VhYHE72CyO3CstJkv1TV1PT-wn_Oracs,12411
11
- json_repair-0.47.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- json_repair-0.47.4.dist-info/entry_points.txt,sha256=SNfge3zPSP-ASqriYU9r3NAPaXdseYr7ciPMKdV2uSw,57
13
- json_repair-0.47.4.dist-info/top_level.txt,sha256=7-VZwZN2CgB_n0NlSLk-rEUFh8ug21lESbsblOYuZqw,12
14
- json_repair-0.47.4.dist-info/RECORD,,