Source code for vmware.vapi.lib.jsonlib
"""
JSON encoder for double values
"""
__author__ = 'VMware, Inc.'
__copyright__ = 'Copyright (c) 2015 VMware, Inc. All rights reserved.'
import decimal
import logging
import math
try:
import simplejson as json
except ImportError:
import json
from vmware.vapi.l10n.runtime import message_factory
from vmware.vapi.exception import CoreException
logger = logging.getLogger(__name__)
[docs]def canonicalize_double(o):
"""
Canonicalize double based on XML schema double canonical format
The exponent must be indicated by "E". Leading zeroes and the
preceding optional "+" sign are prohibited in the exponent. If the
exponent is zero, it must be indicated by "E0". For the mantissa, the
preceding optional "+" sign is prohibited and the decimal point is
required. Leading and trailing zeroes are prohibited subject to the
following: number representations must be normalized such that there
is a single digit which is non-zero to the left of the decimal point
and at least a single digit to the right of the decimal point unless
the value being represented is zero. The canonical representation
for zero is 0.0E0
http://www.w3.org/TR/xmlschema-2/#double
:type o: :class:`decimal.Decimal`
:param o: Decimal object to be canonicalized
:rtype: :class:`str`
:return: Canonical string representation of the decimal
"""
# NaN and INF must not be used
if o.is_infinite() or o.is_nan():
msg = message_factory.get_message(
'vapi.decimal.canonicalization')
logger.debug(msg)
raise CoreException(msg)
str_val = str(o)
# Extract sign of mantissa
neg_sign = ''
if str_val.startswith('-'):
neg_sign = '-'
str_val = str_val[1:]
# Extract mantissa and exponent
mantissa, exponent = None, None
if 'E' in str_val:
mantissa, exponent = str_val.split('E')
else:
mantissa = str_val
# decimal class uses context objects that governs precision
# for arithmetic operations. Setting the precision to the
# length of mantissa string. To canonicalize the mantissa,
# we do a division operation, precision maybe lost if we
# don't set .prec
decimal.getcontext().prec = len(mantissa)
mantissa = decimal.Decimal(mantissa)
exponent = int(exponent) if exponent is not None else 0
# There MUST be a single non zero digit on the left of the decimal point
# (unless a zero is represented)
num_digits = 0
if mantissa:
num_digits = int(math.log10(mantissa))
exponent = exponent + num_digits
if num_digits < 0:
# If the number is of the form 0.[0]+[1-9]+
num_digits *= -1
# since there MUST be a single non zero digit on the left of decimal
# point, we have to multiple by an extra 10
num_digits += 1
exponent -= 1
mantissa = mantissa * int(math.pow(10, num_digits))
else:
# If the number is of the form [1-9]+.[0-9]+
mantissa = mantissa / int(math.pow(10, num_digits))
if mantissa < 1:
# If the original number is of the form 0.[1-9]+ then, num_digits
# would have been 0, multile by extra 10 to get it to
# canonical form
mantissa *= 10
exponent -= 1
# There MUST be at least single digit on the right of the decimal point
mantissa = str(mantissa)
if '.' not in mantissa:
mantissa = '%s.0' % mantissa
# If there are any trailing zeros, strip them off
left, right = mantissa.split('.')
first = right[0]
remaining = right[1:].rstrip('0')
mantissa = '%s.%s%s' % (left, first, remaining)
return '%s%sE%s' % (neg_sign, mantissa, exponent)
[docs]class DecimalEncoder(json.JSONEncoder):
"""
Class that adds capability of encoding decimal
in JSON
"""
# In 2.x version of json library, _iterencode should be overriden
# to have special encoding for objects
def _iterencode(self, o, markers=None):
"""
Overriding the decimal encoding for the default encoder
"""
if isinstance(o, decimal.Decimal):
return (str(o) for o in [o])
return super(DecimalEncoder, self)._iterencode(o, markers) # pylint: disable=W0212
# In 3.x version of json library, default() should be overrriden
# to have special encoding for objects
[docs] def default(self, o): # pylint: disable=E0202
if isinstance(o, decimal.Decimal):
return (str(o) for o in [o])
return json.JSONEncoder.default(self, o)