Bidirectional data structure conversion in Python

  • A+
Category:Languages

Note: this is not a simple two-way map; the conversion is the important part.

I'm writing an application that will send and receive messages with a certain structure, which I must convert from and to an internal structure.

For example, the message:

{     "Person": {         "name": {             "first": "John",             "last": "Smith"         }     },     "birth_date": "1997.01.12",     "points": "330" } 

This must be converted to :

{      "Person": {         "firstname": "John",         "lastname": "Smith",         "birth": datetime.date(1997, 1, 12),         "points": 330     } } 

And vice-versa.

These messages have a lot of information, so I want to avoid having to manually write converters for both directions. Is there any way in Python to specify the mapping once, and use it for both cases?

In my research, I found an interesting Haskell library called JsonGrammar which allows for this (it's for JSON, but that's irrelevant for the case). But my knowledge of Haskell isn't good enough to attempt a port.


That's actually quite an interesting problem. You could define a list of transformation, for example in the form (key1, func_1to2, key2, func_2to1), or a similar format, where key could contain separators to indicate different levels of the dict, like "Person.name.first".

noop = lambda x: x relations = [("Person.name.first", noop, "Person.firstname", noop),              ("Person.name.last", noop, "Person.lastname", noop),              ("birth_date", lambda s: datetime.date(*map(int, s.split("."))),               "Person.birth", lambda d: d.strftime("%Y.%m.%d")),              ("points", int, "Person.points", str)] 

Then, iterate the elements in that list and transform the entries in the dictionary according to whether you want to go from form A to B or vice versa. You will also need some helper function for accessing keys in nested dictionaries using those dot-separated keys.

def deep_get(d, key):     for k in key.split("."):         d = d[k]     return d  def deep_set(d, key, val):     *first, last = key.split(".")     for k in first:         d = d.setdefault(k, {})     d[last] = val  def convert(d, mapping, atob):     res = {}     for a, x, b, y in mapping:         a, b, f = (a, b, x) if atob else (b, a, y)         deep_set(res, b, f(deep_get(d, a)))     return res 

Example:

>>> d1 = {"Person": { "name": { "first": "John", "last": "Smith" } }, ...       "birth_date": "1997.01.12", ...       "points": "330" } ... >>> print(convert(d1, relations, True))     {'Person': {'birth': datetime.date(1997, 1, 12),             'firstname': 'John',             'lastname': 'Smith',             'points': 330}} 

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: