Completed
Pull Request — master (#222)
by
unknown
03:43
created

UaJSONDecoder.dict_to_object()   F

Complexity

Conditions 15

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 15
c 1
b 0
f 0
dl 0
loc 43
rs 2.7451

How to fix   Complexity   

Complexity

Complex classes like UaJSONDecoder.dict_to_object() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
JSON encoder / decoder for OPC UA objects
3
This can be useful for serializing data for external system such as a cloud service or a web app
4
"""
5
6
from json import JSONEncoder, JSONDecoder
7
8
from opcua import ua
9
from opcua import Node
10
from opcua.common.subscription import DataChangeNotif
11
from opcua.common.events import Event
12
from datetime import datetime
13
14
# FIXME this JSON utility is not finished, should implement encoding / decoding of useful UA types
15
# FIXME this class should be improved to use JSON schemas to better serialize UA objects
16
17
18
class UaJSONEncoder(JSONEncoder):
19
    """
20
    JSON encoder utility which is extended to handle opc ua objects
21
    note that JSON pair order may not match the python object attribute order
22
    """
23
    def default(self, o, sort_keys=True):
24
        if isinstance(o, datetime):
25
            return {
26
                '__type__': 'datetime',
27
                'year': o.year,
28
                'month': o.month,
29
                'day': o.day,
30
                'hour': o.hour,
31
                'minute': o.minute,
32
                'second': o.second,
33
                'microsecond': o.microsecond
34
            }
35
        elif isinstance(o, ua.NodeId):
36
            return {
37
                '__type__': 'NodeId',
38
                'Identifier': str(o.Identifier),
39
                'NamespaceIndex': str(o.NamespaceIndex),
40
                'NamespaceUri': o.NamespaceUri,
41
                'NodeIdType': o.NodeIdType.name,
42
43
            }
44
        elif isinstance(o, Node):
45
            return {
46
                '__type__': 'Node',
47
                'NodeId': str(o.nodeid),
48
                'BrowseName': o.get_browse_name().Name,
49
                'DisplayName': o.get_display_name().Text.decode(encoding='UTF-8'),
50
            }
51
        elif isinstance(o, ua.DataValue):
52
            return {
53
                '__type__': 'DataValue',
54
                'ServerTimestamp': o.ServerTimestamp.isoformat(' '),
55
                'SourceTimestamp': o.SourceTimestamp.isoformat(' '),
56
                'StatusCode': o.StatusCode.name,
57
                'VariantType': o.Value.VariantType.name,
58
                'Value': str(o.Value.Value),
59
            }
60
        elif isinstance(o, DataChangeNotif):
61
            return {
62
                '__type__': 'DataChangeNotif',
63
                'Node': str(o.subscription_data.node.nodeid),
64
                'DisplayName': o.subscription_data.node.get_display_name().Text.decode(encoding='UTF-8'),
65
                'ServerTimestamp': o.monitored_item.Value.ServerTimestamp.isoformat(' '),
66
                'SourceTimestamp': o.monitored_item.Value.SourceTimestamp.isoformat(' '),
67
                'StatusCode': o.monitored_item.Value.StatusCode.name,
68
                'VariantType': o.monitored_item.Value.Value.VariantType.name,
69
                'Value': str(o.monitored_item.Value.Value.Value),
70
            }
71
        elif isinstance(o, Event):
72
            return {
73
                '__type__': 'Event',
74
                'SourceNode': str(o.SourceNode),
75
                'EventId': o.EventId,
76
                'EventType': str(o.EventType),
77
                'LocalTime': o.LocalTime.isoformat(' '),
78
                'ReceiveTime': o.ReceiveTime.isoformat(' '),
79
                'Time': o.Time.isoformat(' '),
80
                'Severity': str(o.Severity),
81
                'Message': o.Message.Text.decode(encoding='UTF-8'),
82
            }
83
        else:
84
            return JSONEncoder.default(self, o)
85
86
87
class UaJSONDecoder(JSONDecoder):
88
    """
89
    JSON decoder utility which is extended to handle opc ua objects
90
    Limited functionality; user must check what kind of object was decoded externally
91
    """
92
    def __init__(self):
93
        JSONDecoder.__init__(self, object_hook=self.dict_to_object)
94
95
    def dict_to_object(self, d):
96
        if '__type__' not in d:
97
            return d
98
99
        o_type = d.pop('__type__')
100
        if o_type == 'datetime':
101
            return datetime(**d)
102
        elif o_type == 'NodeId':
103
            node_id_type = d.pop('NodeIdType')
104
105
            if node_id_type == 'Numeric':
106
                identifier = int(d.pop('Identifier'))
107
            elif node_id_type == 'String':
108
                identifier = d.pop('Identifier')
109
            elif node_id_type == 'ByteString':
110
                identifier = bytes(d.pop('Identifier'))
111
            else:
112
                raise ValueError("NodeId object missing NodeIdType attribute")
113
114
            return ua.NodeId(identifier, int(d.pop('NamespaceIndex')))
115
        elif o_type == 'Node':
116
            raise TypeError("Node should not be decoded because it requires a reference to the server")
117
        elif o_type == 'DataValue':
118
            variant_type = d.pop('VariantType')
119
            if variant_type in ("Int8", "UInt8", "Int16", "UInt16", "Int32", "UInt32", "Int64", "UInt64"):
120
                dv = ua.DataValue(ua.Variant(int(d.pop('Value'))))
121
            elif variant_type in ("Float", "Double"):
122
                dv = ua.DataValue(ua.Variant(float(d.pop('Value'))))
123
            elif variant_type in ("Boolean"):
124
                dv = ua.DataValue(ua.Variant(bool(d.pop('Value'))))
125
            elif variant_type in ("ByteString", "String"):
126
                dv = ua.DataValue(ua.Variant(d.pop('Value')))
127
            else:
128
                raise TypeError("DataValue did not have a supported variant type")
129
            return dv
130
        elif o_type == 'DataChangeNotif':
131
            raise TypeError("DataChangeNotif should not be decoded")
132
        elif o_type == 'Event':
133
            raise TypeError("Event should not be decoded")
134
        else:
135
            #  decoder subclass can't handle this object type; put it back in the dict so super throws TypeError
136
            d['__type__'] = o_type
137
            return d
138