2
votes

Simplifier le dictionnaire en python à partir d'un événement de déclenchement Firestore

Je lis les données d'un déclencheur Update Cloud Firestore. L ' événement est un dictionnaire qui contient les données contenues dans la clé [' value '] [' fields '] . Cependant, chacune des clés contient un dictionnaire imbriqué contenant une clé comme 'integerValue' , 'booleanValue' ou 'stringValue' , où la valeur de integerValue est en fait une chaîne. Existe-t-il une méthode pour supprimer les "pointeurs de type"?

Comment puis-je convertir ceci:

{
    'count': 0,
    'verified': False,
    'user': 'Matt',
}

En ceci:

{
    'fields': {
        'count': {
            'integerValue': '0'
        },
        'verified': {
            'booleanValue': False
        },
        'user': {
            'stringValue': 'Matt'
        }
    }
}


2 commentaires

Avez-vous fait une tentative?


@MadPhysicist Pas vraiment parce que je suis nouveau avec python, j'apprends au fur et à mesure. J'ai vérifié les méthodes possibles pour un dictionnaire. Mais ne semble pas trouver quoi que ce soit qui le rend plus simple. J'ai utilisé ce que «Austin» et «Bob» ont mentionné dans les réponses avec un événement plus petit avec un champ, jusqu'à ce que je trouve des dictionnaires plus gros. Avec les fonctions JavaScript, c'est plus facile, vous obtenez simplement l'accès aux données en tant que propriétés. On dirait que je devrai récupérer les données directement à partir du firestore lors du déclenchement de l'événement au lieu d'essayer de les lire à partir de l'événement.


4 Réponses :


1
votes

Il n'y a pas de méthode explicite pour le faire. Une chose que vous pouvez faire est de parcourir le dictionnaire existant en récupérant les éléments dont vous avez besoin dans le nouveau dictionnaire:

d = {
    'fields': {
        'count': {
            'integerValue': '0'
        },
        'verified': {
            'booleanValue': False
        },
        'user': {
            'stringValue': 'Matt'
        }
    }
}

required = ['count', 'verified', 'user']
d1 = {}
for x in d.values():
    for y in required:
        if 'integerValue' in x[y].keys():
            d1[y] = int(list(x[y].values())[0])
        else:
            d1[y] = list(x[y].values())[0]

print(d1)
# {'count': 0, 'verified': False, 'user': 'Matt'}


2 commentaires

Austin Je vois, je l'ai fait quand je testais une fonction et je n'ai pas vraiment remarqué la integerValue jusqu'à ce que mes documents deviennent un peu plus complexes. Il me semble que la meilleure façon de gérer cela dans google-cloud-functions pour python est d'obtenir les données directement de Firestore lors du déclenchement de la fonction au lieu d'essayer de l'analyser comme vous et @Bob avez suggéré.


@Racu, peut-être que ça ferait l'affaire. Nous n'avions aucune idée de l'historique de votre travail, et vous n'en avez pas non plus mentionné davantage dans votre question. De plus, je dois admettre que je n'ai aucune expérience de travail avec Firestore.



2
votes

Utilisez les touches () dans le dictionnaire

origin_dict={
    'fields': {
        'count': {
            'integerValue': '0'
        },
        'verified': {
            'booleanValue': False
        },
        'user': {
            'stringValue': 'Matt'
        }
    }
}
# remove first layer
b = origin_dict['fields']
new_dict = dict()

for i in b.keys():
    # i will be second layer of dictionary
    for j in b[i].keys():
        # j will be third layer of dictionary
        new_dict[i] = b[i][j]
print (new_dict)


1 commentaires

Peut être simplifié en utilisant for i in b: et for j in b [i]: .



2
votes

Vous pouvez créer un mappage des types connus et convertir les valeurs de cette façon:

event['value'] = {k: types[t](v) for k t, v in (k, *d.popitem()) for k, d in event['value']['fields'].items())}

Vous pouvez remplacer un dictionnaire imbriqué comme celui que vous avez grâce à la magie de dict.popitem :

replacement = {}
for key, meta in event['value']['fields'].items():
    typ, value = meta.popitem()
    replacement[key] = types[typ](value)
event['value'] = replacement

Vous pouvez le réduire à une seule ligne avec une compréhension du dictionnaire:

types = {
    'integerValue': int,
    'booleanValue': bool,
    'stringValue': str,
}


0 commentaires

1
votes

Récemment, j'ai rencontré un problème similaire.

Nous pourrions parcourir la carte de manière récursive pour extraire et simplifier les données du déclencheur d'événements.

Voici l'implémentation python, étendue à partir des réponses précédentes.

converter = FirestoreTriggerConverter(client)
simplified_data_dict = converter.convert(event_data_dict["event"]["value"]["fields"])

Utilisez ceci comme suit

class FirestoreTriggerConverter(object):
    def __init__(self, client=None) -> None:
        self.client = client if client else firestore.client()
        self._action_dict = {
        'geoPointValue': (lambda x: dict(x)),
        'stringValue': (lambda x: str(x)),
        'arrayValue': (lambda x: [self._parse_value(value_dict) for value_dict in x["values"]]),
        'booleanValue': (lambda x: bool(x)),
        'nullValue': (lambda x: None),
        'timestampValue': (lambda x: self._parse_timestamp(x)),
        'referenceValue': (lambda x: self._parse_doc_ref(x)),
        'mapValue': (lambda x: {key: self._parse_value(value) for key, value in x["fields"].items()}),
        'integerValue': (lambda x: int(x)),
        'doubleValue': (lambda x: float(x)),
    }

def convert(self, data_dict: dict) -> dict:
    result_dict = {}
    for key, value_dict in data_dict.items():
        result_dict[key] = self._parse_value(value_dict)
    return result_dict

def _parse_value(self, value_dict: dict) -> Any:
    data_type, value = value_dict.popitem()

    return self._action_dict[data_type](value)

def _parse_timestamp(self, timestamp: str):
    try:
        return datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%fZ')
    except ValueError as e:
        return datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%SZ')

def _parse_doc_ref(self, doc_ref: str) -> DocumentReference:
    path_parts = doc_ref.split('/documents/')[1].split('/')
    collection_path = path_parts[0]
    document_path = '/'.join(path_parts[1:])

    doc_ref = self.client.collection(collection_path).document(document_path)
    return doc_ref


0 commentaires