lionagi 0.10.7__py3-none-any.whl → 0.12.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.
- lionagi/adapters/__init__.py +1 -0
- lionagi/fields/file.py +1 -1
- lionagi/fields/reason.py +1 -1
- lionagi/libs/file/concat.py +6 -1
- lionagi/libs/file/concat_files.py +5 -1
- lionagi/libs/file/create_path.py +80 -0
- lionagi/libs/file/file_util.py +358 -0
- lionagi/libs/file/save.py +1 -1
- lionagi/libs/package/imports.py +177 -8
- lionagi/libs/parse/fuzzy_parse_json.py +117 -0
- lionagi/libs/parse/to_dict.py +336 -0
- lionagi/libs/parse/to_json.py +61 -0
- lionagi/libs/parse/to_num.py +378 -0
- lionagi/libs/parse/to_xml.py +57 -0
- lionagi/libs/parse/xml_parser.py +148 -0
- lionagi/libs/schema/breakdown_pydantic_annotation.py +48 -0
- lionagi/protocols/generic/log.py +2 -1
- lionagi/utils.py +123 -921
- lionagi/version.py +1 -1
- {lionagi-0.10.7.dist-info → lionagi-0.12.0.dist-info}/METADATA +8 -11
- {lionagi-0.10.7.dist-info → lionagi-0.12.0.dist-info}/RECORD +24 -30
- lionagi/libs/parse.py +0 -30
- lionagi/tools/browser/__init__.py +0 -0
- lionagi/tools/browser/providers/browser_use_.py +0 -3
- lionagi/tools/code/__init__.py +0 -3
- lionagi/tools/code/coder.py +0 -3
- lionagi/tools/code/manager.py +0 -3
- lionagi/tools/code/providers/__init__.py +0 -3
- lionagi/tools/code/providers/aider_.py +0 -3
- lionagi/tools/code/providers/e2b_.py +0 -3
- lionagi/tools/code/sandbox.py +0 -3
- lionagi/tools/file/manager.py +0 -3
- lionagi/tools/file/providers/__init__.py +0 -3
- lionagi/tools/file/providers/docling_.py +0 -3
- lionagi/tools/file/writer.py +0 -3
- lionagi/tools/query/__init__.py +0 -3
- /lionagi/{tools/browser/providers → libs/parse}/__init__.py +0 -0
- {lionagi-0.10.7.dist-info → lionagi-0.12.0.dist-info}/WHEEL +0 -0
- {lionagi-0.10.7.dist-info → lionagi-0.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
import contextlib
|
2
|
+
import json
|
3
|
+
import re
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
|
7
|
+
def fuzzy_parse_json(
|
8
|
+
str_to_parse: str, /
|
9
|
+
) -> dict[str, Any] | list[dict[str, Any]]:
|
10
|
+
"""
|
11
|
+
Attempt to parse a JSON string, trying a few minimal "fuzzy" fixes if needed.
|
12
|
+
|
13
|
+
Steps:
|
14
|
+
1. Parse directly with json.loads.
|
15
|
+
2. Replace single quotes with double quotes, normalize spacing, and try again.
|
16
|
+
3. Attempt to fix unmatched brackets using fix_json_string.
|
17
|
+
4. If all fail, raise ValueError.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
str_to_parse: The JSON string to parse
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
Parsed JSON (dict or list of dicts)
|
24
|
+
|
25
|
+
Raises:
|
26
|
+
ValueError: If the string cannot be parsed as valid JSON
|
27
|
+
TypeError: If the input is not a string
|
28
|
+
"""
|
29
|
+
_check_valid_str(str_to_parse)
|
30
|
+
|
31
|
+
# 1. Direct attempt
|
32
|
+
with contextlib.suppress(Exception):
|
33
|
+
return json.loads(str_to_parse)
|
34
|
+
|
35
|
+
# 2. Try cleaning: replace single quotes with double and normalize
|
36
|
+
cleaned = _clean_json_string(str_to_parse.replace("'", '"'))
|
37
|
+
with contextlib.suppress(Exception):
|
38
|
+
return json.loads(cleaned)
|
39
|
+
|
40
|
+
# 3. Try fixing brackets
|
41
|
+
fixed = fix_json_string(cleaned)
|
42
|
+
with contextlib.suppress(Exception):
|
43
|
+
return json.loads(fixed)
|
44
|
+
|
45
|
+
# If all attempts fail
|
46
|
+
raise ValueError("Invalid JSON string")
|
47
|
+
|
48
|
+
|
49
|
+
def _check_valid_str(str_to_parse: str, /):
|
50
|
+
if not isinstance(str_to_parse, str):
|
51
|
+
raise TypeError("Input must be a string")
|
52
|
+
if not str_to_parse.strip():
|
53
|
+
raise ValueError("Input string is empty")
|
54
|
+
|
55
|
+
|
56
|
+
def _clean_json_string(s: str) -> str:
|
57
|
+
"""Basic normalization: replace unescaped single quotes, trim spaces, ensure keys are quoted."""
|
58
|
+
# Replace unescaped single quotes with double quotes
|
59
|
+
# '(?<!\\)'" means a single quote not preceded by a backslash
|
60
|
+
s = re.sub(r"(?<!\\)'", '"', s)
|
61
|
+
# Collapse multiple whitespaces
|
62
|
+
s = re.sub(r"\s+", " ", s)
|
63
|
+
# Ensure keys are quoted
|
64
|
+
# This attempts to find patterns like { key: value } and turn them into {"key": value}
|
65
|
+
s = re.sub(r'([{,])\s*([^"\s]+)\s*:', r'\1"\2":', s)
|
66
|
+
return s.strip()
|
67
|
+
|
68
|
+
|
69
|
+
def fix_json_string(str_to_parse: str, /) -> str:
|
70
|
+
"""Try to fix JSON string by ensuring brackets are matched properly."""
|
71
|
+
if not str_to_parse:
|
72
|
+
raise ValueError("Input string is empty")
|
73
|
+
|
74
|
+
brackets = {"{": "}", "[": "]"}
|
75
|
+
open_brackets = []
|
76
|
+
pos = 0
|
77
|
+
length = len(str_to_parse)
|
78
|
+
|
79
|
+
while pos < length:
|
80
|
+
char = str_to_parse[pos]
|
81
|
+
|
82
|
+
if char == "\\":
|
83
|
+
pos += 2 # Skip escaped chars
|
84
|
+
continue
|
85
|
+
|
86
|
+
if char == '"':
|
87
|
+
pos += 1
|
88
|
+
# skip string content
|
89
|
+
while pos < length:
|
90
|
+
if str_to_parse[pos] == "\\":
|
91
|
+
pos += 2
|
92
|
+
continue
|
93
|
+
if str_to_parse[pos] == '"':
|
94
|
+
pos += 1
|
95
|
+
break
|
96
|
+
pos += 1
|
97
|
+
continue
|
98
|
+
|
99
|
+
if char in brackets:
|
100
|
+
open_brackets.append(brackets[char])
|
101
|
+
elif char in brackets.values():
|
102
|
+
if not open_brackets:
|
103
|
+
# Extra closing bracket
|
104
|
+
# Better to raise error than guess
|
105
|
+
raise ValueError("Extra closing bracket found.")
|
106
|
+
if open_brackets[-1] != char:
|
107
|
+
# Mismatched bracket
|
108
|
+
raise ValueError("Mismatched brackets.")
|
109
|
+
open_brackets.pop()
|
110
|
+
|
111
|
+
pos += 1
|
112
|
+
|
113
|
+
# Add missing closing brackets if any
|
114
|
+
if open_brackets:
|
115
|
+
str_to_parse += "".join(reversed(open_brackets))
|
116
|
+
|
117
|
+
return str_to_parse
|
@@ -0,0 +1,336 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import json
|
4
|
+
from collections.abc import Callable, Iterable, Mapping, Sequence
|
5
|
+
from enum import Enum
|
6
|
+
from functools import partial
|
7
|
+
from typing import Any, Literal
|
8
|
+
|
9
|
+
from pydantic import BaseModel
|
10
|
+
|
11
|
+
from lionagi.utils import PydanticUndefinedType, UndefinedType
|
12
|
+
|
13
|
+
|
14
|
+
def to_dict(
|
15
|
+
input_: Any,
|
16
|
+
/,
|
17
|
+
*,
|
18
|
+
use_model_dump: bool = True,
|
19
|
+
fuzzy_parse: bool = False,
|
20
|
+
suppress: bool = False,
|
21
|
+
str_type: Literal["json", "xml"] | None = "json",
|
22
|
+
parser: Callable[[str], Any] | None = None,
|
23
|
+
recursive: bool = False,
|
24
|
+
max_recursive_depth: int = None,
|
25
|
+
recursive_python_only: bool = True,
|
26
|
+
use_enum_values: bool = False,
|
27
|
+
**kwargs: Any,
|
28
|
+
) -> dict[str, Any]:
|
29
|
+
"""
|
30
|
+
Convert various input types to a dictionary, with optional recursive processing.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
input_: The input to convert.
|
34
|
+
use_model_dump: Use model_dump() for Pydantic models if available.
|
35
|
+
fuzzy_parse: Use fuzzy parsing for string inputs.
|
36
|
+
suppress: Return empty dict on errors if True.
|
37
|
+
str_type: Input string type ("json" or "xml").
|
38
|
+
parser: Custom parser function for string inputs.
|
39
|
+
recursive: Enable recursive conversion of nested structures.
|
40
|
+
max_recursive_depth: Maximum recursion depth (default 5, max 10).
|
41
|
+
recursive_python_only: If False, attempts to convert custom types recursively.
|
42
|
+
use_enum_values: Use enum values instead of names.
|
43
|
+
**kwargs: Additional arguments for parsing functions.
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
dict[str, Any]: A dictionary derived from the input.
|
47
|
+
|
48
|
+
Raises:
|
49
|
+
ValueError: If parsing fails and suppress is False.
|
50
|
+
|
51
|
+
Examples:
|
52
|
+
>>> to_dict({"a": 1, "b": [2, 3]})
|
53
|
+
{'a': 1, 'b': [2, 3]}
|
54
|
+
>>> to_dict('{"x": 10}', str_type="json")
|
55
|
+
{'x': 10}
|
56
|
+
>>> to_dict({"a": {"b": {"c": 1}}}, recursive=True, max_recursive_depth=2)
|
57
|
+
{'a': {'b': {'c': 1}}}
|
58
|
+
"""
|
59
|
+
|
60
|
+
try:
|
61
|
+
if recursive:
|
62
|
+
input_ = recursive_to_dict(
|
63
|
+
input_,
|
64
|
+
use_model_dump=use_model_dump,
|
65
|
+
fuzzy_parse=fuzzy_parse,
|
66
|
+
str_type=str_type,
|
67
|
+
parser=parser,
|
68
|
+
max_recursive_depth=max_recursive_depth,
|
69
|
+
recursive_custom_types=not recursive_python_only,
|
70
|
+
use_enum_values=use_enum_values,
|
71
|
+
**kwargs,
|
72
|
+
)
|
73
|
+
|
74
|
+
return _to_dict(
|
75
|
+
input_,
|
76
|
+
fuzzy_parse=fuzzy_parse,
|
77
|
+
parser=parser,
|
78
|
+
str_type=str_type,
|
79
|
+
use_model_dump=use_model_dump,
|
80
|
+
use_enum_values=use_enum_values,
|
81
|
+
**kwargs,
|
82
|
+
)
|
83
|
+
except Exception as e:
|
84
|
+
if suppress or input_ == "":
|
85
|
+
return {}
|
86
|
+
raise e
|
87
|
+
|
88
|
+
|
89
|
+
def recursive_to_dict(
|
90
|
+
input_: Any,
|
91
|
+
/,
|
92
|
+
*,
|
93
|
+
max_recursive_depth: int = None,
|
94
|
+
recursive_custom_types: bool = False,
|
95
|
+
**kwargs: Any,
|
96
|
+
) -> Any:
|
97
|
+
|
98
|
+
if not isinstance(max_recursive_depth, int):
|
99
|
+
max_recursive_depth = 5
|
100
|
+
else:
|
101
|
+
if max_recursive_depth < 0:
|
102
|
+
raise ValueError(
|
103
|
+
"max_recursive_depth must be a non-negative integer"
|
104
|
+
)
|
105
|
+
if max_recursive_depth == 0:
|
106
|
+
return input_
|
107
|
+
if max_recursive_depth > 10:
|
108
|
+
raise ValueError(
|
109
|
+
"max_recursive_depth must be less than or equal to 10"
|
110
|
+
)
|
111
|
+
|
112
|
+
return _recur_to_dict(
|
113
|
+
input_,
|
114
|
+
max_recursive_depth=max_recursive_depth,
|
115
|
+
current_depth=0,
|
116
|
+
recursive_custom_types=recursive_custom_types,
|
117
|
+
**kwargs,
|
118
|
+
)
|
119
|
+
|
120
|
+
|
121
|
+
def _recur_to_dict(
|
122
|
+
input_: Any,
|
123
|
+
/,
|
124
|
+
*,
|
125
|
+
max_recursive_depth: int,
|
126
|
+
current_depth: int = 0,
|
127
|
+
recursive_custom_types: bool = False,
|
128
|
+
**kwargs: Any,
|
129
|
+
) -> Any:
|
130
|
+
|
131
|
+
if current_depth >= max_recursive_depth:
|
132
|
+
return input_
|
133
|
+
|
134
|
+
if isinstance(input_, str):
|
135
|
+
try:
|
136
|
+
# Attempt to parse the string
|
137
|
+
parsed = _to_dict(input_, **kwargs)
|
138
|
+
# Recursively process the parsed result
|
139
|
+
return _recur_to_dict(
|
140
|
+
parsed,
|
141
|
+
max_recursive_depth=max_recursive_depth,
|
142
|
+
current_depth=current_depth + 1,
|
143
|
+
recursive_custom_types=recursive_custom_types,
|
144
|
+
**kwargs,
|
145
|
+
)
|
146
|
+
except Exception:
|
147
|
+
# Return the original string if parsing fails
|
148
|
+
return input_
|
149
|
+
|
150
|
+
elif isinstance(input_, dict):
|
151
|
+
# Recursively process dictionary values
|
152
|
+
return {
|
153
|
+
key: _recur_to_dict(
|
154
|
+
value,
|
155
|
+
max_recursive_depth=max_recursive_depth,
|
156
|
+
current_depth=current_depth + 1,
|
157
|
+
recursive_custom_types=recursive_custom_types,
|
158
|
+
**kwargs,
|
159
|
+
)
|
160
|
+
for key, value in input_.items()
|
161
|
+
}
|
162
|
+
|
163
|
+
elif isinstance(input_, (list, tuple, set)):
|
164
|
+
# Recursively process list or tuple elements
|
165
|
+
processed = [
|
166
|
+
_recur_to_dict(
|
167
|
+
element,
|
168
|
+
max_recursive_depth=max_recursive_depth,
|
169
|
+
current_depth=current_depth + 1,
|
170
|
+
recursive_custom_types=recursive_custom_types,
|
171
|
+
**kwargs,
|
172
|
+
)
|
173
|
+
for element in input_
|
174
|
+
]
|
175
|
+
return type(input_)(processed)
|
176
|
+
|
177
|
+
elif isinstance(input_, type) and issubclass(input_, Enum):
|
178
|
+
try:
|
179
|
+
obj_dict = _to_dict(input_, **kwargs)
|
180
|
+
return _recur_to_dict(
|
181
|
+
obj_dict,
|
182
|
+
max_recursive_depth=max_recursive_depth,
|
183
|
+
current_depth=current_depth + 1,
|
184
|
+
**kwargs,
|
185
|
+
)
|
186
|
+
except Exception:
|
187
|
+
return input_
|
188
|
+
|
189
|
+
elif recursive_custom_types:
|
190
|
+
# Process custom classes if enabled
|
191
|
+
try:
|
192
|
+
obj_dict = _to_dict(input_, **kwargs)
|
193
|
+
return _recur_to_dict(
|
194
|
+
obj_dict,
|
195
|
+
max_recursive_depth=max_recursive_depth,
|
196
|
+
current_depth=current_depth + 1,
|
197
|
+
recursive_custom_types=recursive_custom_types,
|
198
|
+
**kwargs,
|
199
|
+
)
|
200
|
+
except Exception:
|
201
|
+
return input_
|
202
|
+
|
203
|
+
else:
|
204
|
+
# Return the input as is for other data types
|
205
|
+
return input_
|
206
|
+
|
207
|
+
|
208
|
+
def _enum_to_dict(input_, /, use_enum_values: bool = True):
|
209
|
+
dict_ = dict(input_.__members__).copy()
|
210
|
+
if use_enum_values:
|
211
|
+
return {key: value.value for key, value in dict_.items()}
|
212
|
+
return dict_
|
213
|
+
|
214
|
+
|
215
|
+
def _str_to_dict(
|
216
|
+
input_: str,
|
217
|
+
/,
|
218
|
+
fuzzy_parse: bool = False,
|
219
|
+
str_type: Literal["json", "xml"] | None = "json",
|
220
|
+
parser: Callable[[str], Any] | None = None,
|
221
|
+
remove_root: bool = False,
|
222
|
+
root_tag: str = "root",
|
223
|
+
**kwargs: Any,
|
224
|
+
):
|
225
|
+
"""
|
226
|
+
kwargs for parser
|
227
|
+
"""
|
228
|
+
if not parser:
|
229
|
+
if str_type == "xml" and not parser:
|
230
|
+
from lionagi.libs.parse.xml_parser import xml_to_dict
|
231
|
+
|
232
|
+
parser = partial(
|
233
|
+
xml_to_dict, remove_root=remove_root, root_tag=root_tag
|
234
|
+
)
|
235
|
+
|
236
|
+
elif fuzzy_parse:
|
237
|
+
|
238
|
+
from lionagi.libs.parse.fuzzy_parse_json import fuzzy_parse_json
|
239
|
+
|
240
|
+
parser = fuzzy_parse_json
|
241
|
+
else:
|
242
|
+
parser = json.loads
|
243
|
+
|
244
|
+
return parser(input_, **kwargs)
|
245
|
+
|
246
|
+
|
247
|
+
def _na_to_dict(input_: type[None] | UndefinedType | PydanticUndefinedType, /):
|
248
|
+
return {}
|
249
|
+
|
250
|
+
|
251
|
+
def _model_to_dict(input_: Any, /, use_model_dump=True, **kwargs):
|
252
|
+
"""
|
253
|
+
kwargs: built-in serialization methods kwargs
|
254
|
+
accepted built-in serialization methods:
|
255
|
+
- mdoel_dump
|
256
|
+
- to_dict
|
257
|
+
- to_json
|
258
|
+
- dict
|
259
|
+
- json
|
260
|
+
"""
|
261
|
+
|
262
|
+
if use_model_dump and hasattr(input_, "model_dump"):
|
263
|
+
return input_.model_dump(**kwargs)
|
264
|
+
|
265
|
+
methods = (
|
266
|
+
"to_dict",
|
267
|
+
"to_json",
|
268
|
+
"json",
|
269
|
+
"dict",
|
270
|
+
)
|
271
|
+
for method in methods:
|
272
|
+
if hasattr(input_, method):
|
273
|
+
result = getattr(input_, method)(**kwargs)
|
274
|
+
return json.loads(result) if isinstance(result, str) else result
|
275
|
+
|
276
|
+
if hasattr(input_, "__dict__"):
|
277
|
+
return input_.__dict__
|
278
|
+
|
279
|
+
try:
|
280
|
+
return dict(input_)
|
281
|
+
except Exception as e:
|
282
|
+
raise ValueError(f"Unable to convert input to dictionary: {e}")
|
283
|
+
|
284
|
+
|
285
|
+
def _set_to_dict(input_: set, /) -> dict:
|
286
|
+
return {v: v for v in input_}
|
287
|
+
|
288
|
+
|
289
|
+
def _iterable_to_dict(input_: Iterable, /) -> dict:
|
290
|
+
return {idx: v for idx, v in enumerate(input_)}
|
291
|
+
|
292
|
+
|
293
|
+
def _to_dict(
|
294
|
+
input_: Any,
|
295
|
+
/,
|
296
|
+
*,
|
297
|
+
fuzzy_parse: bool = False,
|
298
|
+
str_type: Literal["json", "xml"] | None = "json",
|
299
|
+
parser: Callable[[str], Any] | None = None,
|
300
|
+
remove_root: bool = False,
|
301
|
+
root_tag: str = "root",
|
302
|
+
use_model_dump: bool = True,
|
303
|
+
use_enum_values: bool = True,
|
304
|
+
**kwargs: Any,
|
305
|
+
) -> dict[str, Any]:
|
306
|
+
|
307
|
+
if isinstance(input_, set):
|
308
|
+
return _set_to_dict(input_)
|
309
|
+
|
310
|
+
if isinstance(input_, type) and issubclass(input_, Enum):
|
311
|
+
return _enum_to_dict(input_, use_enum_values=use_enum_values)
|
312
|
+
|
313
|
+
if isinstance(input_, Mapping):
|
314
|
+
return dict(input_)
|
315
|
+
|
316
|
+
if isinstance(input_, type(None) | UndefinedType | PydanticUndefinedType):
|
317
|
+
return _na_to_dict(input_)
|
318
|
+
|
319
|
+
if isinstance(input_, str):
|
320
|
+
return _str_to_dict(
|
321
|
+
input_,
|
322
|
+
fuzzy_parse=fuzzy_parse,
|
323
|
+
str_type=str_type,
|
324
|
+
parser=parser,
|
325
|
+
remove_root=remove_root,
|
326
|
+
root_tag=root_tag,
|
327
|
+
**kwargs,
|
328
|
+
)
|
329
|
+
|
330
|
+
if isinstance(input_, BaseModel) or not isinstance(input_, Sequence):
|
331
|
+
return _model_to_dict(input_, use_model_dump=use_model_dump, **kwargs)
|
332
|
+
|
333
|
+
if isinstance(input_, Iterable):
|
334
|
+
return _iterable_to_dict(input_)
|
335
|
+
|
336
|
+
return dict(input_)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import json
|
2
|
+
import re
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from .fuzzy_parse_json import fuzzy_parse_json
|
6
|
+
|
7
|
+
# Precompile the regex for extracting JSON code blocks
|
8
|
+
_JSON_BLOCK_PATTERN = re.compile(r"```json\s*(.*?)\s*```", re.DOTALL)
|
9
|
+
|
10
|
+
|
11
|
+
def to_json(
|
12
|
+
input_data: str | list[str], /, *, fuzzy_parse: bool = False
|
13
|
+
) -> dict[str, Any] | list[dict[str, Any]]:
|
14
|
+
"""
|
15
|
+
Extract and parse JSON content from a string or markdown code blocks.
|
16
|
+
|
17
|
+
Attempts direct JSON parsing first. If that fails, looks for JSON content
|
18
|
+
within markdown code blocks denoted by ```json.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
input_data (str | list[str]): The input string or list of strings to parse.
|
22
|
+
fuzzy_parse (bool): If True, attempts fuzzy JSON parsing on failed attempts.
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
dict or list of dicts:
|
26
|
+
- If a single JSON object is found: returns a dict.
|
27
|
+
- If multiple JSON objects are found: returns a list of dicts.
|
28
|
+
- If no valid JSON found: returns an empty list.
|
29
|
+
"""
|
30
|
+
|
31
|
+
# If input_data is a list, join into a single string
|
32
|
+
if isinstance(input_data, list):
|
33
|
+
input_str = "\n".join(input_data)
|
34
|
+
else:
|
35
|
+
input_str = input_data
|
36
|
+
|
37
|
+
# 1. Try direct parsing
|
38
|
+
try:
|
39
|
+
if fuzzy_parse:
|
40
|
+
return fuzzy_parse_json(input_str)
|
41
|
+
return json.loads(input_str)
|
42
|
+
except Exception:
|
43
|
+
pass
|
44
|
+
|
45
|
+
# 2. Attempt extracting JSON blocks from markdown
|
46
|
+
matches = _JSON_BLOCK_PATTERN.findall(input_str)
|
47
|
+
if not matches:
|
48
|
+
return []
|
49
|
+
|
50
|
+
# If only one match, return single dict; if multiple, return list of dicts
|
51
|
+
if len(matches) == 1:
|
52
|
+
data_str = matches[0]
|
53
|
+
return (
|
54
|
+
fuzzy_parse_json(data_str) if fuzzy_parse else json.loads(data_str)
|
55
|
+
)
|
56
|
+
|
57
|
+
# Multiple matches
|
58
|
+
if fuzzy_parse:
|
59
|
+
return [fuzzy_parse_json(m) for m in matches]
|
60
|
+
else:
|
61
|
+
return [json.loads(m) for m in matches]
|