def _anchor_node(self, field): if field: anchored_node = self.root_node.get(field, None) # check if anchored_node is dict-like if anchored_node is None: return anchored_node elif isinstance(anchored_node, Mapping): return DataNode(anchored_node) # or if anchored_node is actually a list elif isinstance(anchored_node, list): return [DataNode(node) for node in anchored_node] else: raise InvalidNode(field)
def _apply_node_spec(self, node: DataNode, anchor: str, spec: dict): filter_spec = spec.get(SPEC_FILTER) if not self._passes(filter_spec, node): return {} result = DataNode() for field_name, field_spec in spec.items(): # skip reserved field if not field_name.startswith(KEYWORD_MARKER): self._check_if_readable(field_spec) field_value = None if isinstance(field_spec, list): field_value = self._apply_field_spec(node, field_spec) elif isinstance(field_spec, dict): field_value = self.map(using=field_spec, on=anchor) if field_value is not None: result[field_name] = field_value return result.as_dict()
def _passes(filter_spec: list, node: DataNode): if filter_spec is None: return True filter_field = filter_spec[0] value = node.get(filter_field) passing = True if value is not None: filter_args = [value] filter_args.extend(filter_spec[2:]) do_filter = filter_spec[1] passing = bool(do_filter(*filter_args)) return passing
def _apply_field_spec(node: DataNode, spec: list): source_field_name = spec[0] if source_field_name in [SPEC_OBJECT_LITERAL, SPEC_ARRAY_LITERAL]: field_value = JsonMapper._get_object_literal(spec) else: field_value = node.get(source_field_name) has_customisation = len(spec) > 1 if has_customisation: operation = JsonMapper.get_post_process_func(spec[1]) args = [field_value] args.extend(spec[2:]) field_value = operation(*args) return field_value
def __init__(self, source: dict): self.root_node = DataNode(source)
class JsonMapper: def __init__(self, source: dict): self.root_node = DataNode(source) def map(self, using={}, on=''): spec = using self._check_if_readable(spec) anchor = self._determine_anchor(on, spec) node = self.root_node if not anchor else self._anchor_node(anchor) if node is None: return node elif isinstance(node, list): result = [] for item in node: mapping = self._apply_node_spec(item, anchor, spec) if len(mapping) > 0: result.append(mapping) else: result = self._apply_node_spec(node, anchor, spec) return result @staticmethod def _check_if_readable(spec): if not ((isinstance(spec, list) or isinstance(spec, dict)) and len(spec) > 0): raise UnreadableSpecification @staticmethod def _determine_anchor(field, spec): anchor = field if SPEC_ANCHOR in spec: anchor = f'{anchor}.{spec[SPEC_ANCHOR]}' if anchor else spec[SPEC_ANCHOR] return anchor def _anchor_node(self, field): if field: anchored_node = self.root_node.get(field, None) # check if anchored_node is dict-like if anchored_node is None: return anchored_node elif isinstance(anchored_node, Mapping): return DataNode(anchored_node) # or if anchored_node is actually a list elif isinstance(anchored_node, list): return [DataNode(node) for node in anchored_node] else: raise InvalidNode(field) def _apply_node_spec(self, node: DataNode, anchor: str, spec: dict): filter_spec = spec.get(SPEC_FILTER) if not self._passes(filter_spec, node): return {} result = DataNode() for field_name, field_spec in spec.items(): # skip reserved field if not field_name.startswith(KEYWORD_MARKER): self._check_if_readable(field_spec) field_value = None if isinstance(field_spec, list): field_value = self._apply_field_spec(node, field_spec) elif isinstance(field_spec, dict): field_value = self.map(using=field_spec, on=anchor) if field_value is not None: result[field_name] = field_value return result.as_dict() @staticmethod def _passes(filter_spec: list, node: DataNode): if filter_spec is None: return True filter_field = filter_spec[0] value = node.get(filter_field) passing = True if value is not None: filter_args = [value] filter_args.extend(filter_spec[2:]) do_filter = filter_spec[1] passing = bool(do_filter(*filter_args)) return passing @staticmethod def _apply_field_spec(node: DataNode, spec: list): source_field_name = spec[0] if source_field_name in [SPEC_OBJECT_LITERAL, SPEC_ARRAY_LITERAL]: field_value = JsonMapper._get_object_literal(spec) else: field_value = node.get(source_field_name) has_customisation = len(spec) > 1 if has_customisation: operation = JsonMapper.get_post_process_func(spec[1]) args = [field_value] args.extend(spec[2:]) field_value = operation(*args) return field_value @staticmethod def get_post_process_func(func): if callable(func): return func return manager[func] @staticmethod def _get_object_literal(spec): if len(spec) != 2: raise UnreadableSpecification('Expecting exactly 1 JSON literal value.') field_value = spec[1] if not (isinstance(field_value, Mapping) or isinstance(field_value, list)): raise UnreadableSpecification('JSON literal should be a dict-like or list structure.') if len(field_value) == 0: field_value = None return field_value