0
# Serialization
1
2
JSON encoding and decoding capabilities for Griffe objects. This system enables serializing complete API structures to JSON format for storage, transmission, or external processing, and deserializing them back to full Griffe objects.
3
4
## Capabilities
5
6
### JSON Encoding
7
8
Custom JSON encoder for serializing Griffe objects to JSON format.
9
10
```python { .api }
11
import json
12
13
class JSONEncoder(json.JSONEncoder):
14
"""
15
JSON encoder for Griffe objects.
16
17
Enables serialization of Griffe data structures to JSON format.
18
Handles all Griffe object types including modules, classes, functions,
19
attributes, aliases, and their associated metadata.
20
"""
21
22
def default(self, obj: Any) -> Any:
23
"""
24
Convert Griffe objects to JSON-serializable formats.
25
26
Args:
27
obj: Object to serialize (Griffe object or standard type)
28
29
Returns:
30
Any: JSON-serializable representation
31
32
Raises:
33
TypeError: If object cannot be serialized
34
35
Examples:
36
Basic usage:
37
>>> encoder = griffe.JSONEncoder()
38
>>> json_str = json.dumps(module, cls=griffe.JSONEncoder)
39
40
With custom options:
41
>>> encoder = griffe.JSONEncoder(indent=2, sort_keys=True)
42
>>> json_str = encoder.encode(module)
43
"""
44
```
45
46
### JSON Decoding
47
48
Function for deserializing JSON data back to Griffe objects.
49
50
```python { .api }
51
def json_decoder(dct: dict[str, Any]) -> dict[str, Any] | Object:
52
"""
53
Decode Griffe objects from JSON.
54
55
JSON decoder that reconstructs Griffe objects from their serialized
56
representations. Works with JSON data produced by JSONEncoder.
57
58
Args:
59
dct: Dictionary from JSON decoder containing object data
60
61
Returns:
62
dict[str, Any] | Object: Decoded Griffe object or plain dictionary
63
64
Examples:
65
Decode from JSON string:
66
>>> json_data = '{"kind": "module", "name": "example", ...}'
67
>>> obj = json.loads(json_data, object_hook=griffe.json_decoder)
68
69
Decode from file:
70
>>> with open("api.json") as f:
71
... data = json.load(f, object_hook=griffe.json_decoder)
72
"""
73
```
74
75
### Object Serialization Methods
76
77
Built-in serialization methods available on all Griffe objects.
78
79
```python { .api }
80
# Available on all Object subclasses (Module, Class, Function, etc.)
81
82
def serialize(self, **kwargs: Any) -> dict[str, Any]:
83
"""
84
Serialize the object to a dictionary.
85
86
Converts the Griffe object to a dictionary representation
87
suitable for JSON encoding or other serialization formats.
88
89
Args:
90
**kwargs: Serialization options including:
91
- full: Include all details (default: False)
92
- docstring_parser: Parser for docstrings
93
- docstring_options: Options for docstring parsing
94
95
Returns:
96
dict[str, Any]: Dictionary representation of the object
97
98
Examples:
99
Basic serialization:
100
>>> data = module.serialize()
101
102
Full serialization with all details:
103
>>> data = module.serialize(full=True)
104
105
With docstring parsing:
106
>>> data = module.serialize(
107
... docstring_parser="google",
108
... docstring_options={"style": "google"}
109
... )
110
"""
111
112
def as_json(self, **kwargs: Any) -> str:
113
"""
114
Serialize the object to JSON string.
115
116
Convenience method that combines serialize() and JSON encoding
117
in a single call.
118
119
Args:
120
**kwargs: Arguments passed to serialize() and json.dumps()
121
122
Returns:
123
str: JSON string representation
124
125
Examples:
126
Basic JSON export:
127
>>> json_str = module.as_json()
128
129
Pretty-printed JSON:
130
>>> json_str = module.as_json(indent=2)
131
132
Full serialization to JSON:
133
>>> json_str = module.as_json(full=True, indent=2)
134
"""
135
136
@classmethod
137
def from_json(cls, json_str: str, **kwargs: Any) -> Object:
138
"""
139
Deserialize an object from JSON string.
140
141
Class method that reconstructs a Griffe object from its
142
JSON representation.
143
144
Args:
145
json_str: JSON string to deserialize
146
**kwargs: Deserialization options
147
148
Returns:
149
Object: Reconstructed Griffe object
150
151
Examples:
152
Deserialize module:
153
>>> json_data = module.as_json()
154
>>> restored = griffe.Module.from_json(json_data)
155
156
Deserialize any object type:
157
>>> restored = griffe.Object.from_json(json_data)
158
"""
159
```
160
161
## Usage Examples
162
163
### Basic Serialization
164
165
```python
166
import griffe
167
import json
168
169
# Load a package
170
package = griffe.load("requests")
171
172
# Serialize to dictionary
173
data = package.serialize()
174
print(f"Package data keys: {list(data.keys())}")
175
176
# Serialize to JSON string
177
json_str = package.as_json(indent=2)
178
print("JSON representation:")
179
print(json_str[:200] + "...")
180
181
# Use custom JSON encoder
182
encoder = griffe.JSONEncoder(indent=2, sort_keys=True)
183
custom_json = encoder.encode(package)
184
```
185
186
### Full API Serialization
187
188
```python
189
import griffe
190
191
# Load package with full details
192
package = griffe.load("mypackage")
193
194
# Serialize with complete information
195
full_data = package.serialize(
196
full=True,
197
docstring_parser="google",
198
docstring_options={"style": "google"}
199
)
200
201
# Export to JSON file
202
with open("api_documentation.json", "w") as f:
203
json.dump(full_data, f, cls=griffe.JSONEncoder, indent=2)
204
205
print(f"Exported API documentation to api_documentation.json")
206
```
207
208
### Deserialization
209
210
```python
211
import griffe
212
import json
213
214
# Load from JSON file
215
with open("api_documentation.json") as f:
216
restored_package = json.load(f, object_hook=griffe.json_decoder)
217
218
print(f"Restored package: {restored_package.name}")
219
print(f"Modules: {list(restored_package.modules.keys())}")
220
221
# Alternative: use from_json class method
222
json_str = open("api_documentation.json").read()
223
restored = griffe.Module.from_json(json_str)
224
```
225
226
### Roundtrip Serialization
227
228
```python
229
import griffe
230
231
# Original package
232
original = griffe.load("requests")
233
234
# Serialize and deserialize
235
json_data = original.as_json(full=True)
236
restored = griffe.Module.from_json(json_data)
237
238
# Compare
239
print(f"Original: {original.name}")
240
print(f"Restored: {restored.name}")
241
print(f"Same classes: {set(original.classes.keys()) == set(restored.classes.keys())}")
242
243
# Detailed comparison
244
def compare_objects(obj1, obj2, path=""):
245
"""Compare two Griffe objects recursively."""
246
if obj1.name != obj2.name:
247
print(f"Name mismatch at {path}: {obj1.name} != {obj2.name}")
248
249
if type(obj1) != type(obj2):
250
print(f"Type mismatch at {path}: {type(obj1)} != {type(obj2)}")
251
252
# Compare specific attributes based on object type
253
if hasattr(obj1, 'functions') and hasattr(obj2, 'functions'):
254
if set(obj1.functions.keys()) != set(obj2.functions.keys()):
255
print(f"Function mismatch at {path}")
256
257
compare_objects(original, restored)
258
```
259
260
### CLI Integration
261
262
```python
263
import griffe
264
import sys
265
266
def export_api_json(package_name: str, output_file: str, full: bool = False):
267
"""Export package API to JSON file."""
268
try:
269
# Load package
270
package = griffe.load(package_name)
271
272
# Serialize
273
if full:
274
data = package.serialize(full=True, docstring_parser="auto")
275
else:
276
data = package.serialize()
277
278
# Export
279
with open(output_file, "w") as f:
280
json.dump(data, f, cls=griffe.JSONEncoder, indent=2)
281
282
print(f"✅ Exported {package_name} API to {output_file}")
283
return 0
284
285
except Exception as e:
286
print(f"❌ Error exporting API: {e}")
287
return 1
288
289
# Command-line usage
290
if __name__ == "__main__":
291
if len(sys.argv) < 3:
292
print("Usage: python script.py <package_name> <output_file> [--full]")
293
sys.exit(1)
294
295
package_name = sys.argv[1]
296
output_file = sys.argv[2]
297
full = "--full" in sys.argv
298
299
exit_code = export_api_json(package_name, output_file, full)
300
sys.exit(exit_code)
301
```
302
303
### Custom Serialization
304
305
```python
306
import griffe
307
import json
308
from typing import Any
309
310
class CustomAPIExporter:
311
"""Custom API exporter with filtering and transformation."""
312
313
def __init__(
314
self,
315
include_private: bool = False,
316
include_docstrings: bool = True,
317
transform_names: bool = False
318
):
319
self.include_private = include_private
320
self.include_docstrings = include_docstrings
321
self.transform_names = transform_names
322
323
def export_module(self, module: griffe.Module) -> dict[str, Any]:
324
"""Export module with custom filtering."""
325
data = {
326
"name": module.name,
327
"type": "module",
328
"path": module.path,
329
}
330
331
if self.include_docstrings and module.docstring:
332
data["docstring"] = module.docstring.value
333
334
# Filter and export functions
335
functions = {}
336
for name, func in module.functions.items():
337
if not self.include_private and name.startswith("_"):
338
continue
339
functions[name] = self.export_function(func)
340
data["functions"] = functions
341
342
# Filter and export classes
343
classes = {}
344
for name, cls in module.classes.items():
345
if not self.include_private and name.startswith("_"):
346
continue
347
classes[name] = self.export_class(cls)
348
data["classes"] = classes
349
350
return data
351
352
def export_function(self, func: griffe.Function) -> dict[str, Any]:
353
"""Export function with custom format."""
354
data = {
355
"name": func.name,
356
"type": "function",
357
"signature": func.signature,
358
}
359
360
if self.include_docstrings and func.docstring:
361
data["docstring"] = func.docstring.value
362
363
# Transform parameter names if requested
364
params = []
365
for param in func.parameters:
366
param_data = {"name": param.name, "kind": param.kind.name}
367
if param.annotation:
368
param_data["type"] = str(param.annotation)
369
if param.default:
370
param_data["default"] = str(param.default)
371
params.append(param_data)
372
373
data["parameters"] = params
374
return data
375
376
def export_class(self, cls: griffe.Class) -> dict[str, Any]:
377
"""Export class with custom format."""
378
data = {
379
"name": cls.name,
380
"type": "class",
381
"bases": [str(base) for base in cls.bases],
382
}
383
384
if self.include_docstrings and cls.docstring:
385
data["docstring"] = cls.docstring.value
386
387
# Export methods
388
methods = {}
389
for name, method in cls.methods.items():
390
if not self.include_private and name.startswith("_"):
391
continue
392
methods[name] = self.export_function(method)
393
data["methods"] = methods
394
395
return data
396
397
# Use custom exporter
398
exporter = CustomAPIExporter(
399
include_private=False,
400
include_docstrings=True,
401
transform_names=True
402
)
403
404
package = griffe.load("requests")
405
custom_data = exporter.export_module(package)
406
407
with open("custom_api.json", "w") as f:
408
json.dump(custom_data, f, indent=2)
409
```
410
411
### Incremental Serialization
412
413
```python
414
import griffe
415
import json
416
import hashlib
417
from pathlib import Path
418
419
class IncrementalAPISerializer:
420
"""Serialize only changed parts of API."""
421
422
def __init__(self, cache_dir: str = ".griffe_cache"):
423
self.cache_dir = Path(cache_dir)
424
self.cache_dir.mkdir(exist_ok=True)
425
426
def get_object_hash(self, obj: griffe.Object) -> str:
427
"""Get hash of object for change detection."""
428
data = obj.serialize()
429
json_str = json.dumps(data, sort_keys=True, cls=griffe.JSONEncoder)
430
return hashlib.sha256(json_str.encode()).hexdigest()
431
432
def serialize_if_changed(self, obj: griffe.Object, force: bool = False) -> bool:
433
"""Serialize object only if it has changed."""
434
obj_hash = self.get_object_hash(obj)
435
cache_file = self.cache_dir / f"{obj.path}.json"
436
hash_file = self.cache_dir / f"{obj.path}.hash"
437
438
# Check if already cached and unchanged
439
if not force and hash_file.exists():
440
with open(hash_file) as f:
441
cached_hash = f.read().strip()
442
if cached_hash == obj_hash:
443
return False # No changes
444
445
# Serialize the object
446
data = obj.serialize(full=True)
447
with open(cache_file, "w") as f:
448
json.dump(data, f, cls=griffe.JSONEncoder, indent=2)
449
450
# Update hash
451
with open(hash_file, "w") as f:
452
f.write(obj_hash)
453
454
return True # Changes detected and serialized
455
456
# Use incremental serializer
457
serializer = IncrementalAPISerializer()
458
459
package = griffe.load("mypackage")
460
for module_name, module in package.modules.items():
461
if serializer.serialize_if_changed(module):
462
print(f"Serialized changed module: {module_name}")
463
else:
464
print(f"No changes in module: {module_name}")
465
```
466
467
## Types
468
469
```python { .api }
470
import json
471
from typing import Any, dict
472
473
# JSON encoder/decoder types
474
JSONEncoder = json.JSONEncoder
475
476
# Serialization function signatures
477
def serialize(**kwargs: Any) -> dict[str, Any]: ...
478
def as_json(**kwargs: Any) -> str: ...
479
def from_json(json_str: str, **kwargs: Any) -> Object: ...
480
def json_decoder(dct: dict[str, Any]) -> dict[str, Any] | Object: ...
481
482
# Core object type
483
from griffe import Object
484
```