Another approach is to adopt a concept from FEEL (the Friendly Enough Expression Language) defined in DMN (Decision Model Notation) - namely @strings.
Any string starting with @" and ending with " is decoded separately with FEEL decoding. Of course the sender and the receiver have to agree to this convention, but ... the code below lets you encode lots of other things as well as dates, times, date/times, timedeltas.
You can encode year/month durations and ranges (so long as you except a 4 element tuple of chr, expr, expr, chr as being a good representation of a range - where the two chrs are open/close brackets).
So, @"P4Y2M" is a duration of 4 years and 2 months. @"P2DT5H" is a timedelta of 2 days and 4 hours, @"(2021-01-02 .. 2021-12-31)" is a year range.
The following code can be used to serialize and de-serialize @strings.
import datetime
import pySFeel
parser = pySFeel.SFeelParser()
def convertAtString(thisString):
# Convert an @string
(status, newValue) = parser.sFeelParse(thisString[2:-1])
if 'errors' in status:
return thisString
else:
return newValue
def convertIn(newValue):
if isinstance(newValue, dict):
for key in newValue:
if isinstance(newValue[key], int):
newValue[key] = float(newValue[key])
elif isinstance(newValue[key], str) and (newValue[key][0:2] == '@"') and (newValue[key][-1] == '"'):
newValue[key] = convertAtString(newValue[key])
elif isinstance(newValue[key], dict) or isinstance(newValue[key], list):
newValue[key] = convertIn(newValue[key])
elif isinstance(newValue, list):
for i in range(len(newValue)):
if isinstance(newValue[i], int):
newValue[i] = float(newValue[i])
elif isinstance(newValue[i], str) and (newValue[i][0:2] == '@"') and (newValue[i][-1] == '"'):
newValue[i] = convertAtString(newValue[i])
elif isinstance(newValue[i], dict) or isinstance(newValue[i], list):
newValue[i] = convertIn(newValue[i])
elif isinstance(newValue, str) and (newValue[0:2] == '@"') and (newValue[-1] == '"'):
newValue = convertAtString(newValue)
return newValue
def convertOut(thisValue):
if isinstance(thisValue, datetime.date):
return '@"' + thisValue.isoformat() + '"'
elif isinstance(thisValue, datetime.datetime):
return '@"' + thisValue.isoformat(sep='T') + '"'
elif isinstance(thisValue, datetime.time):
return '@"' + thisValue.isoformat() + '"'
elif isinstance(thisValue, datetime.timedelta):
sign = ''
duration = thisValue.total_seconds()
if duration < 0:
duration = -duration
sign = '-'
secs = duration % 60
duration = int(duration / 60)
mins = duration % 60
duration = int(duration / 60)
hours = duration % 24
days = int(duration / 24)
return '@"%sP%dDT%dH%dM%fS"' % (sign, days, hours, mins, secs)
elif isinstance(thisValue, bool):
return thisValue:
elif thisValue is None:
return thisValue:
elif isinstance(thisValue, int):
sign = ''
if thisValue < 0:
thisValue = -thisValue
sign = '-'
years = int(thisValue / 12)
months = (thisValue % 12)
return '@"%sP%dY%dM"' % (sign, years, months)
elif isinstance(thisValue, tuple) and (len(thisValue) == 4):
(lowEnd, lowVal, highVal, highEnd) = thisValue
return '@"' + lowEnd + str(lowVal) + ' .. ' + str(highVal) + highEnd
elif thisValue is None:
return 'null'
elif isinstance(thisValue, dict):
for item in thisValue:
thisValue[item] = convertOut(thisValue[item])
return thisValue
elif isinstance(thisValue, list):
for i in range(len(thisValue)):
thisValue[i] = convertOut(thisValue[i])
return thisValue
else:
return thisValue
JSON
as a handler. I have posted a pymongo specific answer. – Insuperabledatetime.datetime
, see Making object JSON serializable with regular encoder. – Alacritytime.time()
) that is portable and you can calldatetime.datetime.fromtimestamp
when you need a pythondatetime
. – Selfdetermination