Convertir un JSON en conservant l'ordre des clés

a marqué ce sujet comme résolu.
Auteur du sujet

Bonsoir !

Lors de mon stage j’ai du ré-écrire un logiciel qu’ils utilisent dans mon entreprise. Ce logiciel prend en entré un fichier JSON. Or j’ai un peu modifié ce fichier lors de ma ré-écriture il faut donc que je convertisse tous les anciens fichiers vers le nouveau format. J’ai donc écrit un petit script Python qui fonctionnais sans problème jusqu’à ce que je me rendre compte que l’ordre des clés n’est plus respecté ! (Logique avec des dictionnaires vous me direz…)

Je suis donc en train de ré-écrire ce petit script pour qu’il garde l’ordre des clés avec des OrderedDict mais c’est assez galère à utiliser je trouve. Pour mieux comprendre, voici un fichier JSON avec l’ancien format :

[
    {
        "DT": "Test",
        "Directory": "Test",
        "Destination": "Test",
        "Item": "Manuel",
        "Actif": "Y",
        "ID": "Test",
        "Template": "Test",
        "DocumentMaitre": "Test",
        "Variables": {
            "MUtitre": "Test",
            "orientation": "portrait"
        },
        "Branches": {
            "E2": "Y",
            "LDV": "N"
        },
        "documents" : [
            {
                "Item": "Document",
                "Actif": "Y",
                "Template": "Test",
                "DocumentMaitre": "Test",
                "Variables": {
                    "MUtitre": "Test",
                    "orientation": "portrait"
                },
                "Branches": {
                    "E2": "Y",
                    "LDV": "N"
                }
            }
        ]
    }
]

Et en voici le même converti, tel qu’il faut qu’il soit :

[
    {
        "DT": "Test",
        "Sources": "Test",
        "Destination": "Test",
        "Active": "Y",
        "ID": "Test",
        "Template": "Test",
        "Master": "Test",
        "Lang": "en",
        "Variables": {
            "MUtitre": "Test",
            "orientation": "portrait"
        },
        "Branches": {
            "E2": true,
            "LDV": false
        },
        "Documents" : [
            {
                "Item": "Document",
                "Active": true,
                "Template": "Test",
                "Master": "Test",
                "Variables": {
                    "MUtitre": "Test",
                    "orientation": "portrait"
                },
                "Branches": {
                    "E2": true,
                    "LDV": false
                }
            }
        ]
    }
]

On a donc des clés renomés (Directory => Sources, Actif => Active, DocumentMaitre => Master, documents=> Documents) tous les "Y" deviennent true et les "N"deviennent false et enfin une nouvelle clé apparaît avant la clé "Branches", "Lang" = "en".

Voici ce que j’ai fait pour le moment :

import json

from collections import OrderedDict


def change_key_name(json_data, old_key, new_key):
    if old_key in json_data.keys():
        new_data = json_data.__class__()
        for key, value in json_data.items():
            if key == old_key:
                new_data[new_key] = value
            else:
                new_data[key] = value
        return new_data
    return json_data


def convert_booleans(json_data):
    for key, value in json_data.items():
        if isinstance(value, dict):
            convert_booleans(value)
        elif value in ("Y", "N"):
            json_data[key] = True if value == "Y" else False


def convert(json_data):
    convert_booleans(json_data)
    new_data = change_key_name(json_data, "Directory", "Sources")
    new_data = change_key_name(new_data, "DocumentMaitre", "Master")
    new_data = change_key_name(new_data, "Actif", "Active")
    new_data = change_key_name(new_data, "documents", "Documents")

    # La partie ci-dessous ne fonctionne pas du tout, je me suis perdu dans mon code récursif je pense
    if "Documents" in new_data.keys():
        for i in range(len(new_data["Documents"])):
            print(new_data["Documents"][i])
            new_data["Documents"][i] = convert(new_data["Documents"][i])

        return new_data


if __name__ == '__main__':
    with open("config.txt", "r", encoding="utf-8") as json_file:
        data = json.load(json_file, object_pairs_hook=OrderedDict)
        for i in range(len(data)):
            data[i] = convert(data[i])

        print(data)

Le problème dans ma sortie est que j’ai ('Documents', [None]) alors que je ne devrais pas avoir None.

Est-ce que vous sauriez pourquoi ?

Merci !

+0 -0

Bonjour,

Ta fonction convert retourne systématiquement None si le dictionnaire ne contient pas de clé "Documents".

Par ailleurs tu gagnerais en clarté en évitant les for i in range(len(...)), et en préférant construire de nouveaux objets plutôt que modifier les existants.

Auteur du sujet

Exact, mauvaise indentation sur le return new_data. Ce qui me donne le code corrigé et fonctionnel suivant :

def convert(json_data):
    convert_booleans(json_data)
    new_data = change_key_name(json_data, "Directory", "Sources")
    new_data = change_key_name(new_data, "DocumentMaitre", "Master")
    new_data = change_key_name(new_data, "Actif", "Active")
    new_data = change_key_name(new_data, "documents", "Documents")

    if "Documents" in new_data.keys():
        for i in range(len(new_data["Documents"])):
            new_data["Documents"][i] = convert(new_data["Documents"][i])

    return new_data

Que veux-tu dire par :

Par ailleurs tu gagnerais en clarté en évitant les for i in range(len(...)), et en préférant construire de nouveaux objets plutôt que modifier les existants.

entwanne

?

Merci pour ton aide !

+0 -0

Si le problème se limite à ce qui est décrit ici, je pense que tu pourrais simplifier en traitant au niveau du décodage, comme ceci:

def obj_pairs_hook(pairs):
    keys_exchanges = {
        "Directory": "Sources",
        "DocumentMaitre": "Master",
        "Actif": "Active",
        "documents": "Documents",
    }
    lst = []
    for k, v in pairs:
        if k in keys_exchanges:
            k = keys_exchanges[k]
        if v in ('Y', 'N'):
            v = (v == 'Y')
        lst.append((k, v))
    return OrderedDict(lst)

On peut même étendre ce code pour l’ajout de la clé "Lang", à condition que cet ajout soit récursif (tu indiques que l’ajout doit se faire avant la clé "Branch", mais dans ton exemple ce n’est pas récursif). Ce ne sera sans doute pas très "propre", mais ça reste le plus simple.

Si les fichiers sont bien formatés, tu peux également envisager une approche plus simple, sans aucune (dé)sérialisation, en substituant directement dans le texte.

Édité par yoch

+0 -0

Que veux-tu dire par :

Par ailleurs tu gagnerais en clarté en évitant les for i in range(len(...)), et en préférant construire de nouveaux objets plutôt que modifier les existants.

entwanne

?

Merci pour ton aide !

Wizix

Je veux dire qu’il faut toujours garder à l’esprit que l’on travaille sur des itérables, et que cela permet pas mal d’opérations. Dans ton cas, enumerate pourrait faire l’affaire par exemple quand tu as réellement besoin de connaître l’indice dans la liste.

Mais la suite de mon intervention était pour dire qu’il serait encore plus simple de ne jamais se soucier de cet index, et donc de ne pas remplacer les éléments de la liste actuelle mais de créer une nouvelle liste.

Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte