mardi 31 décembre 2019

Design of a parser of a complex message structure

I'm working on a parser that is supposed to convert 1 type of a message from string to JSON, and another type of message from JSON to string.

The 2 types of messages can be expanded to more types further ahead, and have a common structure.

enter image description here

So far I've built the structure of the messages using OOP approach. It gives me the flexibility to add more types of messages in the future, and divides the responsibility for each part of the message between the different classes.

I created an interface called Parsable, which contains 2 static methods: json_build, and txt_build. The txt_build function takes a string as an argument, checks some of its format (the part which is relevant to the class), and returns an instance of the relevant class, with all the fields set to the contents of the message.

For instance, if I have a message like this one: "MESSAGE

MESSAGE_TYPE A

COMMON_FIELD1 SOME_VALUE

COMMON FIELD2 SOME_OTHER_VALUE

BODY

STOP"

Then txt_build with the message as an argument would return a MessageA instance with common_field1, and common_field2 set to SOME_VALUE, and SOME_OTHER_VALUE respectively.

The BODY contains fields which are unique to each type of message. I didn't mention it in order to simplify matters.

In addition, I would like to have an option to convert a JSON object from JSON to txt using the json_build method.

So that a json like this one:

{
    "type": "B",
    "common_field1": "SOME_VALUE",
    "common_field2": "SOME_OTHER_VALUE"
    "body": {
         ...
    }
}

Would also give me a MessageB instance with the fields set as above.

After having an instance of one class with all the fields set to their values, I can call the get_json(), or get_txt() methods in order to get the JSON representation or the string representation (according to some specified format I'm using) of the message.

I started to implement the part which build MessageA instances by strings and the code looks somewhat like this:

class Message:

@staticmethod
def txt_build(txt):
    parsed = ...  # code that parses the message

    common_field1, common_field2 = parsed['common_field1'], parsed['common_field2']

    msg_dispatch = {
        'A': MessageA,
        'B': MessageB
    }

    return msg_dispatch[parsed['msg_type']](common_field1, common_field2, msg_body)


class MessageA:
    def __init__(self, common_field1, common_field2, msg_body):
        super(MessageA, self).__init__(common_field1, common_field2)
        # code that parses the msg_body and init fields unique to MessageA


class MessageB:
    pass

The code works well for parsing text and initializing the fields. The problem is that I use the constructor of the sub-classes in order to give the body of the message (which is the only relevant part for each subclass), so I can't use it again for parsing JSON objects, at least not in a pretty way.

The only solution which I thought of, which answers good code practices (according to me, at least), is duplicating the entire message hierarchy so that the first one would parse JSON, and the other one would parse strings.

I would like to know if you have any other solutions, which would simplify things. Maybe I haven't noticed a much simpler solution, or didn't think of using some design pattern which is suitable in this case. I feel like I've made things complicated unnecessarily, and I don't have anyone to consult with.

I'd love to hear any suggestions or new ideas on how to do things differently, and I hope I made myself clear.

Thanks a lot :)

Aucun commentaire:

Enregistrer un commentaire