
# Fichier: python_cheats/cheatsheets/json.txt
# Cheatsheet JSON Python - Guide Complet


[OK] IMPORT
import json
from json import JSONEncoder, JSONDecoder
from pathlib import Path
from typing import Any, Dict, List


[OK] ENCODER (PYTHON -> JSON STRING)


data = {'name': 'Alice', 'age': 30, 'active': True}

# Basique
json_string = json.dumps(data)
# '{"name": "Alice", "age": 30, "active": true}'

# Avec indentation
json_string = json.dumps(data, indent=2)
json_string = json.dumps(data, indent=4)

# Compact (sans espaces)
json_string = json.dumps(data, separators=(',', ':'))


[OK] DÉCODER (JSON STRING -> PYTHON)


json_string = '{"name": "Alice", "age": 30}'
data = json.loads(json_string)
# {'name': 'Alice', 'age': 30}

# Avec gestion d'erreur
try:
    data = json.loads(json_string)
except json.JSONDecodeError as e:
    print(f"Erreur JSON: {e}")


[OK] LIRE DEPUIS UN FICHIER


# Méthode 1: avec open()
with open('data.json', 'r', encoding='utf-8') as f:
    data = json.load(f)

# Méthode 2: avec Path (recommandé)
from pathlib import Path
data = json.loads(Path('data.json').read_text(encoding='utf-8'))

# Lire avec gestion d'erreur
def load_json_safe(filepath: str) -> Dict:
    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            return json.load(f)
    except FileNotFoundError:
        print(f"Fichier {filepath} introuvable")
        return {}
    except json.JSONDecodeError as e:
        print(f"Erreur de décodage JSON: {e}")
        return {}


[OK] ÉCRIRE DANS UN FICHIER


data = {'name': 'Alice', 'age': 30}

# Basique
with open('data.json', 'w', encoding='utf-8') as f:
    json.dump(data, f)

# Avec indentation
with open('data.json', 'w', encoding='utf-8') as f:
    json.dump(data, f, indent=2)

# Avec toutes les options
with open('data.json', 'w', encoding='utf-8') as f:
    json.dump(data, f, indent=2, ensure_ascii=False, sort_keys=True)

# Avec Path
Path('data.json').write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding='utf-8')

# Créer le dossier si nécessaire
def save_json(filepath: str, data: Dict) -> None:
    Path(filepath).parent.mkdir(parents=True, exist_ok=True)
    with open(filepath, 'w', encoding='utf-8') as f:
        json.dump(data, f, indent=2, ensure_ascii=False)


[OK] OPTIONS DE DUMPS/DUMP


json.dumps(data, indent=2)                    # Indentation (lisible)
json.dumps(data, indent=4)                    # Indentation 4 espaces
json.dumps(data, sort_keys=True)              # Trier les clés alphabétiquement
json.dumps(data, ensure_ascii=False)          # Garder les accents (é, à, ç)
json.dumps(data, separators=(',', ':'))       # Compact (pas d'espaces)
json.dumps(data, separators=(', ', ': '))     # Avec espaces
json.dumps(data, default=str)                 # Convertir objets non-sérialisables en str
json.dumps(data, allow_nan=False)             # Interdire NaN, Infinity

# Combinaison d'options (recommandé pour fichiers)
json.dumps(data, indent=2, ensure_ascii=False, sort_keys=True)


[OK] TYPES DE DONNÉES SUPPORTÉS


# Python -> JSON
data = {
    'dict': {'a': 1},           # -> object
    'list': [1, 2, 3],          # -> array
    'tuple': (1, 2),            # -> array
    'str': 'texte',             # -> string
    'int': 42,                  # -> number
    'float': 3.14,              # -> number
    'bool_true': True,          # -> true
    'bool_false': False,        # -> false
    'none': None                # -> null
}

# [ATTENTION] Non supportés directement:
# - set, frozenset
# - datetime, date, time
# - Decimal
# - bytes
# - Objets personnalisés


[OK] OBJETS PERSONNALISÉS (ENCODAGE)


from datetime import datetime, date
from decimal import Decimal

# Méthode 1: Encodeur personnalisé
class CustomEncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        if isinstance(obj, date):
            return obj.isoformat()
        if isinstance(obj, Decimal):
            return float(obj)
        if isinstance(obj, set):
            return list(obj)
        if isinstance(obj, bytes):
            return obj.decode('utf-8')
        if hasattr(obj, '__dict__'):
            return obj.__dict__
        return super().default(obj)

data = {
    'timestamp': datetime.now(),
    'date': date.today(),
    'price': Decimal('19.99'),
    'tags': {'python', 'json'}
}
json_string = json.dumps(data, cls=CustomEncoder, indent=2)

# Méthode 2: Fonction default
def json_serializer(obj):
    if isinstance(obj, (datetime, date)):
        return obj.isoformat()
    if isinstance(obj, Decimal):
        return float(obj)
    if isinstance(obj, set):
        return list(obj)
    raise TypeError(f"Type {type(obj)} non sérialisable")

json.dumps(data, default=json_serializer)

# Méthode 3: Conversion préalable (simple)
json.dumps(data, default=str)  # Convertit tout en string


[OK] OBJETS PERSONNALISÉS (DÉCODAGE)


# Décodeur personnalisé
class CustomDecoder(JSONDecoder):
    def __init__(self, *args, **kwargs):
        super().__init__(object_hook=self.object_hook, *args, **kwargs)
    
    def object_hook(self, obj):
        # Convertir les timestamps ISO en datetime
        for key, value in obj.items():
            if isinstance(value, str):
                try:
                    obj[key] = datetime.fromisoformat(value)
                except (ValueError, AttributeError):
                    pass
        return obj

json_string = '{"timestamp": "2025-10-26T10:30:00"}'
data = json.loads(json_string, cls=CustomDecoder)

# Ou avec object_hook directement
def date_decoder(dct):
    for key, value in dct.items():
        if isinstance(value, str) and 'timestamp' in key:
            try:
                dct[key] = datetime.fromisoformat(value)
            except ValueError:
                pass
    return dct

data = json.loads(json_string, object_hook=date_decoder)


[OK] PRETTY PRINT


import json
from pprint import pprint

data = {'a': 1, 'b': [2, 3], 'c': {'d': 4, 'e': [5, 6]}}

# Méthode 1: json.dumps
print(json.dumps(data, indent=2))

# Méthode 2: json.dumps avec ensure_ascii=False (pour accents)
print(json.dumps(data, indent=2, ensure_ascii=False))

# Méthode 3: pprint (pour objets Python)
pprint(data)
pprint(data, indent=4)

# Méthode 4: json.tool en ligne de commande
# cat data.json | python -m json.tool
# python -m json.tool data.json output.json


[OK] VALIDATION JSON


# Valider une string JSON
def is_valid_json(json_string: str) -> bool:
    try:
        json.loads(json_string)
        return True
    except (json.JSONDecodeError, TypeError):
        return False

# Valider un fichier JSON
def is_valid_json_file(filepath: str) -> bool:
    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            json.load(f)
        return True
    except (json.JSONDecodeError, FileNotFoundError, TypeError):
        return False

# Valider avec détails d'erreur
def validate_json(json_string: str) -> tuple[bool, str]:
    try:
        json.loads(json_string)
        return True, "JSON valide"
    except json.JSONDecodeError as e:
        return False, f"Erreur ligne {e.lineno}, col {e.colno}: {e.msg}"


[OK] MERGE JSON (FUSION)


json1 = {'a': 1, 'b': 2}
json2 = {'b': 3, 'c': 4}

# Méthode 1: Opérateur ** (shallow)
merged = {**json1, **json2}
# {'a': 1, 'b': 3, 'c': 4}

# Méthode 2: dict.update()
merged = json1.copy()
merged.update(json2)

# Méthode 3: Fusion profonde (deep merge)
def deep_merge(dict1: Dict, dict2: Dict) -> Dict:
    result = dict1.copy()
    for key, value in dict2.items():
        if key in result and isinstance(result[key], dict) and isinstance(value, dict):
            result[key] = deep_merge(result[key], value)
        else:
            result[key] = value
    return result

json1 = {'user': {'name': 'Alice', 'age': 30}}
json2 = {'user': {'age': 31, 'city': 'Paris'}}
merged = deep_merge(json1, json2)
# {'user': {'name': 'Alice', 'age': 31, 'city': 'Paris'}}


[OK] NESTED JSON (NAVIGATION)


data = {
    'user': {
        'name': 'Alice',
        'address': {
            'city': 'Paris',
            'country': 'France',
            'coords': {
                'lat': 48.8566,
                'lon': 2.3522
            }
        },
        'contacts': [
            {'type': 'email', 'value': 'alice@example.com'},
            {'type': 'phone', 'value': '+33123456789'}
        ]
    }
}

# Accès direct
city = data['user']['address']['city']

# Accès sécurisé avec get()
city = data.get('user', {}).get('address', {}).get('city', 'Unknown')

# Fonction helper pour navigation profonde
def get_nested(data: Dict, *keys, default=None):
    for key in keys:
        try:
            data = data[key]
        except (KeyError, TypeError, IndexError):
            return default
    return data

lat = get_nested(data, 'user', 'address', 'coords', 'lat')
# 48.8566

# Modifier valeur imbriquée
def set_nested(data: Dict, keys: List[str], value: Any) -> None:
    for key in keys[:-1]:
        data = data.setdefault(key, {})
    data[keys[-1]] = value

set_nested(data, ['user', 'address', 'zipcode'], '75001')


[OK] JSON LINES (JSONL / NDJSON)


items = [
    {'id': 1, 'name': 'Alice'},
    {'id': 2, 'name': 'Bob'},
    {'id': 3, 'name': 'Charlie'}
]

# Écrire JSONL
with open('data.jsonl', 'w', encoding='utf-8') as f:
    for item in items:
        f.write(json.dumps(item, ensure_ascii=False) + '\n')

# Lire JSONL
with open('data.jsonl', 'r', encoding='utf-8') as f:
    items = [json.loads(line) for line in f if line.strip()]

# Lire JSONL (avec gestion d'erreur)
def read_jsonl(filepath: str) -> List[Dict]:
    items = []
    with open(filepath, 'r', encoding='utf-8') as f:
        for i, line in enumerate(f, 1):
            try:
                if line.strip():
                    items.append(json.loads(line))
            except json.JSONDecodeError as e:
                print(f"Erreur ligne {i}: {e}")
    return items

# Append à un JSONL
def append_jsonl(filepath: str, item: Dict) -> None:
    with open(filepath, 'a', encoding='utf-8') as f:
        f.write(json.dumps(item, ensure_ascii=False) + '\n')


[OK] FILTRAGE ET TRANSFORMATION


data = {
    'user': 'alice',
    'password': 'secret123',
    'email': 'alice@example.com',
    'age': 30
}

# Filtrer clés (exclure password)
filtered = {k: v for k, v in data.items() if k != 'password'}

# Filtrer plusieurs clés
exclude = {'password', 'internal_id'}
filtered = {k: v for k, v in data.items() if k not in exclude}

# Garder seulement certaines clés
keep = {'user', 'email'}
filtered = {k: v for k, v in data.items() if k in keep}

# Transformer valeurs
transformed = {k: str(v).upper() if isinstance(v, str) else v 
               for k, v in data.items()}

# Filtrer valeurs None
cleaned = {k: v for k, v in data.items() if v is not None}

# Aplatir JSON imbriqué
def flatten_json(data: Dict, parent_key: str = '', sep: str = '.') -> Dict:
    items = []
    for k, v in data.items():
        new_key = f"{parent_key}{sep}{k}" if parent_key else k
        if isinstance(v, dict):
            items.extend(flatten_json(v, new_key, sep=sep).items())
        else:
            items.append((new_key, v))
    return dict(items)

nested = {'user': {'name': 'Alice', 'address': {'city': 'Paris'}}}
flat = flatten_json(nested)
# {'user.name': 'Alice', 'user.address.city': 'Paris'}


[OK] COMPARAISON JSON


json1 = {'a': 1, 'b': 2, 'c': 3}
json2 = {'a': 1, 'b': 3, 'd': 4}

# Comparer directement
are_equal = json1 == json2  # False

# Trouver différences
def json_diff(dict1: Dict, dict2: Dict) -> Dict:
    diff = {
        'added': {},
        'removed': {},
        'modified': {}
    }
    
    all_keys = set(dict1.keys()) | set(dict2.keys())
    
    for key in all_keys:
        if key not in dict1:
            diff['added'][key] = dict2[key]
        elif key not in dict2:
            diff['removed'][key] = dict1[key]
        elif dict1[key] != dict2[key]:
            diff['modified'][key] = {'old': dict1[key], 'new': dict2[key]}
    
    return diff

# Comparer en ignorant l'ordre des clés
def json_equal(obj1, obj2):
    return json.dumps(obj1, sort_keys=True) == json.dumps(obj2, sort_keys=True)


[OK] STREAMING JSON (GRANDES DONNÉES)


import ijson  # pip install ijson

# Pour très gros fichiers JSON (ne pas charger en mémoire)
# Lire un array JSON élément par élément
def stream_json_array(filepath: str):
    with open(filepath, 'rb') as f:
        parser = ijson.items(f, 'item')
        for item in parser:
            yield item
            # Traiter item sans charger tout le fichier

# Exemple d'utilisation
# for user in stream_json_array('users.json'):
#     process(user)


[OK] JSON SCHEMA VALIDATION


# pip install jsonschema
from jsonschema import validate, ValidationError

# Définir un schéma
schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "number", "minimum": 0},
        "email": {"type": "string", "format": "email"}
    },
    "required": ["name", "age"]
}

# Valider
data = {"name": "Alice", "age": 30, "email": "alice@example.com"}

try:
    validate(instance=data, schema=schema)
    print("[OK] JSON valide selon le schéma")
except ValidationError as e:
    print(f"[X] Erreur de validation: {e.message}")


[OK] JSON ET API WEB


import requests

# Envoyer JSON à une API
data = {'username': 'alice', 'email': 'alice@example.com'}
response = requests.post('https://api.example.com/users', json=data)

# Recevoir JSON d'une API
response = requests.get('https://api.example.com/users/1')
user = response.json()

# Headers pour JSON
headers = {'Content-Type': 'application/json'}
response = requests.post(url, data=json.dumps(data), headers=headers)


[OK] EXEMPLES PRATIQUES


# 1. Configuration d'application
config = {
    'app': {
        'name': 'MyApp',
        'version': '1.0.0',
        'debug': False
    },
    'database': {
        'host': 'localhost',
        'port': 5432,
        'name': 'mydb',
        'credentials': {
            'username': 'admin',
            'password': 'secret'
        }
    },
    'logging': {
        'level': 'INFO',
        'file': 'app.log'
    }
}

with open('config.json', 'w', encoding='utf-8') as f:
    json.dump(config, f, indent=2, ensure_ascii=False)

# 2. Cache JSON
class JsonCache:
    def __init__(self, filepath: str):
        self.filepath = filepath
        self.data = self.load()
    
    def load(self) -> Dict:
        try:
            with open(self.filepath, 'r', encoding='utf-8') as f:
                return json.load(f)
        except FileNotFoundError:
            return {}
    
    def save(self) -> None:
        with open(self.filepath, 'w', encoding='utf-8') as f:
            json.dump(self.data, f, indent=2, ensure_ascii=False)
    
    def get(self, key: str, default=None):
        return self.data.get(key, default)
    
    def set(self, key: str, value: Any) -> None:
        self.data[key] = value
        self.save()

# 3. API Response
response = {
    'status': 'success',
    'message': 'Données récupérées avec succès',
    'data': [
        {'id': 1, 'name': 'Item 1', 'price': 19.99},
        {'id': 2, 'name': 'Item 2', 'price': 29.99}
    ],
    'meta': {
        'total': 2,
        'page': 1,
        'per_page': 10
    },
    'timestamp': datetime.now().isoformat()
}

# 4. Logs structurés JSON
import logging
from datetime import datetime

def json_log(level: str, message: str, **kwargs):
    log_entry = {
        'timestamp': datetime.now().isoformat(),
        'level': level,
        'message': message,
        **kwargs
    }
    print(json.dumps(log_entry, ensure_ascii=False))

json_log('INFO', 'User login', user_id=123, ip='192.168.1.1')

# 5. Sauvegarde de données utilisateur
class UserData:
    def __init__(self, filepath: str):
        self.filepath = filepath
        self.data = self.load()
    
    def load(self) -> Dict:
        if Path(self.filepath).exists():
            return json.loads(Path(self.filepath).read_text(encoding='utf-8'))
        return {'users': [], 'settings': {}}
    
    def save(self) -> None:
        Path(self.filepath).write_text(
            json.dumps(self.data, indent=2, ensure_ascii=False),
            encoding='utf-8'
        )
    
    def add_user(self, user: Dict) -> None:
        self.data['users'].append(user)
        self.save()


[OK] BONNES PRATIQUES


# 1. Toujours spécifier encoding='utf-8'
# 2. Utiliser ensure_ascii=False pour les accents
# 3. Utiliser indent=2 pour fichiers lisibles
# 4. Gérer les exceptions JSONDecodeError
# 5. Valider les données avant sérialization
# 6. Ne jamais stocker de mots de passe en clair
# 7. Utiliser des encodeurs personnalisés pour types complexes
# 8. Préférer JSONL pour grandes quantités de données
# 9. Utiliser json.tool pour débugger: python -m json.tool file.json
# 10. Documenter la structure JSON (schéma, types attendus)


[OK] ERREURS COURANTES


# [X] Erreur: TypeError: Object of type datetime is not JSON serializable
[OK] Solution: Utiliser cls=CustomEncoder ou default=str

# [X] Erreur: json.decoder.JSONDecodeError
[OK] Solution: Vérifier que la string est du JSON valide

# [X] Erreur: UnicodeDecodeError
[OK] Solution: Spécifier encoding='utf-8' lors de l'ouverture

# [X] Erreur: KeyError lors de l'accès
[OK] Solution: Utiliser .get() ou try/except

# [X] Erreur: Circular reference (référence circulaire)
[OK] Solution: Éviter les références circulaires ou gérer avec encodeur


[OK] RESSOURCES


# Documentation officielle:
# https://docs.python.org/3/library/json.html

# JSON Schema:
# https://json-schema.org/

# jsonschema (validation):
# pip install jsonschema

# ijson (streaming):
# pip install ijson

# orjson (plus rapide):
# pip install orjson

# ujson (ultra rapide):
# pip install ujson