Как лучше написать несколько предложений try для приведения строкового объекта json к классу данных?

У меня есть функция, которая получает несколько разных строковых объектов json с разными именами структур и/или полей, например:

event = '{"userId": "TDQIQb2fQaORKvCyepDYoZgsoEE3", "profileIsCreated": true}'

or

event = '{"userId": "TDQIQb2fQaORKvCyepDYoZgsoEE3", "signUpFinished": true}'

И у меня есть такие классы данных:

from dataclasses import dataclass, field


@dataclass_json(letter_case=LetterCase.CAMEL)
@dataclass(frozen=True)
class UserId:
    userId: str


@dataclass_json(letter_case=LetterCase.CAMEL)
@dataclass(frozen=True)
class SignUpFinished(UserId):
    signUpFinished: bool


@dataclass_json(letter_case=LetterCase.CAMEL)
@dataclass(frozen=True)
class UserProfileCreated(UserId):
    profileIsCreated: bool

В настоящее время я пишу свою функцию следующим образом:

def cast_event(event):
    user_details = None

    try:
        user_details = SignUpFinished.from_json(event)
    except KeyError:
        pass

    try:
        user_details = UserProfileCreated.from_json(event)
    except KeyError:
        pass

    if user_details:
        return "OK"
    else:
        return "UNHANDLED"

Проблема в том, что по мере того, как мне нужно обрабатывать все больше и больше событий, моя функция будет становиться все длиннее и длиннее, однако она делает только то же самое.

Есть ли лучший способ достичь того, чего я хочу достичь?


Я проверил некоторые вопросы SO:

но они не кажутся лучшим способом добиться того, чего я хочу.


person billydh    schedule 16.01.2020    source источник
comment
Я бы посоветовал попробовать здесь более формальное разграничение, например {"event": "signupFinished", "data": {...}}, которое затем легко позволяет вам включить клавишу event, чтобы определить, с какой структурой данных вы имеете дело. Брут-форс кажется плохим подходом, и он станет беспорядочным, если два разных класса данных смогут десериализовать один и тот же JSON.   -  person deceze♦    schedule 16.01.2020
comment
Могу я подтвердить, что вы имели в виду? поэтому при отправке event в функцию cast_event я должен добавить тип события в качестве поля в полезной нагрузке, как вы показали? а фактическое событие будет в data? Итак, я думаю, мне нужно найти dict или что-то еще, чтобы использовать правильный класс данных?   -  person billydh    schedule 16.01.2020
comment
Да, точно. Настройте его так, чтобы вы могли сопоставить одно значение с определенным классом данных, вместо того, чтобы в основном смотреть на все сообщение в целом, чтобы решить, какой это класс данных.   -  person deceze♦    schedule 16.01.2020
comment
Можете ли вы опубликовать ответ с некоторым рабочим кодом?   -  person billydh    schedule 16.01.2020


Ответы (3)


Поскольку каждый случай синтаксически одинаков, вы можете обрабатывать их в одном цикле. Перебрать последовательность случаев и try до return; это автоматически продолжает попытки более поздних случаев, пока один из них не добьется успеха.

def cast_event(event):
    for case in (UserId , SignUpFinished, UserProfileCreated):
        try:
            return case.from_json(event)
        except KeyError:
            pass
    raise ValueError(f'not a valid event: {event}')
person MisterMiyagi    schedule 16.01.2020

В то время как циклический подход работает для решения вашего вопроса, как было задано, было бы намного лучше, если бы вам не нужна была "грубая сила". " подход к десериализации ваших данных в первую очередь. Для этого вам понадобится поле, которое однозначно поможет вам определить, с какой структурой данных вы имеете дело. Например.:

event = {'event': 'profile',
         'data': {'userId': 'TDQIQb2fQaORKvCyepDYoZgsoEE3', 'profileIsCreated': True}}

Здесь за событием 'profile' всегда будет следовать объект с ключами 'userId' и 'profileIsCreated'. Это гарантия того, что ваши сообщения о событиях должны быть сделаны, тогда их легко проанализировать:

event_map = {
    'profile': UserProfileCreated,
    ...
}

return event_map[event['event']](**event['data'])

Обратите внимание, что здесь я пропускаю этап парсинга JSON. Вам нужно будет сначала проанализировать JSON, чтобы оценить его ключ event, поэтому использование dataclass_json, вероятно, будет излишним/бесполезным.

person deceze♦    schedule 16.01.2020
comment
Вы можете добавить часть, где вы анализируете json, в dataclass? Я не уверен, полностью ли я понимаю ваше предложение или нет. - person billydh; 17.01.2020
comment
В этой последней строке проанализированные данные JSON передаются конструктору класса данных. Фактический синтаксический анализ JSON здесь опущен, но это простой вызов json.load где-то. - person deceze♦; 17.01.2020

Для указанных исходных данных можно сделать так:

import json

data = '{"userId": "TDQIQb2fQaORKvCyepDYoZgsoEE3", "profileIsCreated": true}'

data = json.loads(data)

user_id = data.pop('userId')
user_details_key = list(data.keys())[0] if data else None
user_details = list(data.values())[0] if data else None

assert user_id == 'TDQIQb2fQaORKvCyepDYoZgsoEE3'
assert user_details_key == 'profileIsCreated'
assert user_details == True
person Evgeniy_Burdin    schedule 26.05.2020