text: """Json schema validator module."""
  # -*- coding: utf-8 -*-
  
  from re import match
  from datetime import datetime
  
  from json import loads
  
  try:
      from json.decoder import JSONDecodeError
  except ImportError:
      JSONDecodeError = ValueError
  
  try:
      UNICODE = unicode
  except NameError:
      unicode = str
  
  ERRORS = {
      '1': 'INVALID PAYLOAD',
      '2': 'INVALID DATA TYPE'
  }
  
  
  class JsonValidator:
      """Json Schema validator."""
  
      def __init__(self, constrain, lazy=False, decode_error=None,
                   data_error=None):
          """Set the constrain in object."""
          if not isinstance(constrain, dict):
              raise AttributeError('constrain must be a dict')
          self.constrain = constrain
          self.lazy = lazy
          self.decode_error = decode_error
          self.data_error = data_error
  
      def validate(self, data, constrain=None):
          """Validate incoming data."""
          res = {}
          errors = {}
          data, err = self._convert(data)
  
          if err and err in ('1', '2'):
              return (None, (self.decode_error or {
                  'payload': ERRORS.get(err, '')}))
  
          stack = []
          constrain = constrain or self.constrain
          my_field = ''
  
          if constrain:
              stack.append((res, errors, my_field, data, constrain))
  
          while stack:
              temp_res, temp_errors, my_field, data, constrain = stack.pop()
  
              if constrain is None:
                  continue
  
              for key, rule in constrain.items():
                  field = my_field + '.' + key if my_field else key
  
                  if key not in data:
                      if rule.get('default', False):
                          if callable(rule['default']):
                              temp_res[key] = rule['default']()
                          else:
                              temp_res[key] = rule['default']
                      else:
                          temp_errors[field] = rule.get('error', 'Missing field')
                          if self.lazy:
                              break
                  else:
                      self._key_match(data[key], rule, key, field, temp_res,
                                      temp_errors, stack)
                      if temp_errors and self.lazy:
                          self.clean_data(res)
                          return res, errors
  
          self.clean_data(errors)
          self.clean_data(res)
          return res, errors
  
      def _key_match(self, my_obj, my_rules, my_key, my_field, my_res, my_errors,
                     stack):
          """Validate object with rueles."""
          matcheds = [(my_obj, my_rules, my_key, my_field, my_res, my_errors)]
  
          while matcheds:
              obj, rules, key, field, res, errors = matcheds.pop()
              splitted = field.split('.')
              err_field = field
  
              try:
                  err_field = int(
                      splitted[len(splitted) - 1]) if splitted else field
              except ValueError:
                  pass
  
              if not isinstance(obj, (rules.get('type', str),
                                      rules.get('type', unicode))):
                  if self.special_types(obj, rules, key, err_field, res, errors):
                      if errors and self.lazy:
                          return
                  else:
                      errors[err_field] = rules.get(
                          'type_error', 'Bad data type')
                      if self.lazy:
                          return
  
              elif self._extra_validations(obj, rules, errors, field):
                  if self.lazy:
                      return
  
              elif isinstance(obj, dict):
                  res[key] = {}
                  errors[key] = {}
                  stack.append((res[key], errors[key], field, obj,
                                rules.get('properties', None)))
  
              elif isinstance(obj, list):
                  res[key] = []
                  errors[key] = []
                  my_field2 = field
                  for ind, item in enumerate(obj):
                      field = my_field2
                      field += '.{}'.format(ind)
                      res[key].append(None)
                      errors[key].append(None)
  
                      matcheds.append((item, rules.get('items', {}), ind, field,
                                       res[key], errors[key]))
  
              else:
                  res[key] = obj
  
          return res, errors
  
      @staticmethod
      def clean_data(_error, _key=None, _parent=None):
          """Clean empty errors."""
          stack = []
          stack.append((_error, _key, _parent, True))
  
          while stack:
              error, key, parent, repeat = stack.pop()
  
              if isinstance(error, dict):
                  if repeat:
                      stack.append((error, key, parent, False))
                      for _key in error:
                          stack.append((error[_key], _key, error, True))
  
              elif isinstance(error, list):
                  if repeat:
                      stack.append((error, key, parent, False))
                      for aux, item in enumerate(error):
                          stack.append((item, aux, error, True))
  
              if not error and parent:
                  if isinstance(parent, dict):
                      if key in parent:
                          del parent[key]
                  else:
                      parent.pop(key)
  
      @staticmethod
      def special_types(obj, rules, key, field, res, errors):
          """Validate if special type retrieved."""
          if rules.get('type', str) == datetime:
              if rules.get('dformat', False):
                  try:
                      res[key] = datetime.strptime(obj, rules['dformat'])
                  except ValueError:
                      errors[field] = rules.get(
                          'dformat_error', 'Invalid format')
              else:
                  raise AttributeError('Missing `dformat` on datetime rule')
              return True
          return False
  
      @staticmethod
      def _extra_validations(data, rules, errors, field):
          """Extras validations."""
          if isinstance(data, (int, float)):
              if rules.get('gt', False) and not data > rules['gt']:
                  errors[field] = rules.get(
                      'gt_error', 'Not greater than {limit}').format(
                          value=data, limit=rules['gt'])
                  return True
  
              elif rules.get('lt', False) and not data < rules['lt']:
                  errors[field] = rules.get(
                      'lt_error', 'Not less than {limit}').format(
                          value=data, limit=rules['lt'])
                  return True
  
          if rules.get('format', False):
              if not match(rules['format'], data):
                  errors[field] = rules.get(
                      'format_error', 'Invalid format')
                  return True
  
          if rules.get('in', False):
              if data not in rules['in']:
                  errors[field] = rules.get('in_error', 'Invalid')
                  return True
  
          return False
  
      @staticmethod
      def _convert(data):
          """Check if given data is a string, and loads it."""
          if isinstance(data, (str, dict, list)):
              if isinstance(data, (str, unicode)):
                  try:
                      return (loads(data), False)
                  except JSONDecodeError:
                      return (False, '1')
              else:
                  return (data, False)
          return (False, '2')
  