text: import typing
  from typing import Any  # noqa
  from typing import MutableMapping  # noqa
  import json
  
  from mitmproxy.coretypes import serializable
  from mitmproxy.utils import typecheck
  
  
  class StateObject(serializable.Serializable):
      """
      An object with serializable state.
  
      State attributes can either be serializable types(str, tuple, bool, ...)
      or StateObject instances themselves.
      """
  
      _stateobject_attributes: MutableMapping[str, Any] = None
      """
      An attribute-name -> class-or-type dict containing all attributes that
      should be serialized. If the attribute is a class, it must implement the
      Serializable protocol.
      """
  
      def get_state(self):
          """
          Retrieve object state.
          """
          state = {}
          for attr, cls in self._stateobject_attributes.items():
              val = getattr(self, attr)
              state[attr] = get_state(cls, val)
          return state
  
      def set_state(self, state):
          """
          Load object state from data returned by a get_state call.
          """
          state = state.copy()
          for attr, cls in self._stateobject_attributes.items():
              val = state.pop(attr)
              if val is None:
                  setattr(self, attr, val)
              else:
                  curr = getattr(self, attr)
                  if hasattr(curr, "set_state"):
                      curr.set_state(val)
                  else:
                      setattr(self, attr, make_object(cls, val))
          if state:
              raise RuntimeWarning("Unexpected State in __setstate__: {}".format(state))
  
  
  def _process(typeinfo: typecheck.Type, val: typing.Any, make: bool) -> typing.Any:
      if val is None:
          return None
      elif make and hasattr(typeinfo, "from_state"):
          return typeinfo.from_state(val)
      elif not make and hasattr(val, "get_state"):
          return val.get_state()
  
      typename = str(typeinfo)
  
      if typename.startswith("typing.List"):
          T = typecheck.sequence_type(typeinfo)
          return [_process(T, x, make) for x in val]
      elif typename.startswith("typing.Tuple"):
          Ts = typecheck.tuple_types(typeinfo)
          if len(Ts) != len(val):
              raise ValueError("Invalid data. Expected {}, got {}.".format(Ts, val))
          return tuple(
              _process(T, x, make) for T, x in zip(Ts, val)
          )
      elif typename.startswith("typing.Dict"):
          k_cls, v_cls = typecheck.mapping_types(typeinfo)
          return {
              _process(k_cls, k, make): _process(v_cls, v, make)
              for k, v in val.items()
          }
      elif typename.startswith("typing.Any"):
          # This requires a bit of explanation. We can't import our IO layer here,
          # because it causes a circular import. Rather than restructuring the
          # code for this, we use JSON serialization, which has similar primitive
          # type restrictions as tnetstring, to check for conformance.
          try:
              json.dumps(val)
          except TypeError:
              raise ValueError(f"Data not serializable: {val}")
          return val
      else:
          return typeinfo(val)
  
  
  def make_object(typeinfo: typecheck.Type, val: typing.Any) -> typing.Any:
      """Create an object based on the state given in val."""
      return _process(typeinfo, val, True)
  
  
  def get_state(typeinfo: typecheck.Type, val: typing.Any) -> typing.Any:
      """Get the state of the object given as val."""
      return _process(typeinfo, val, False)
  