public static void deserialize(VersionedObject versionedObject, byte[] data, MutableOffset offset)
      throws VersionedSerializationException {
    VersionStack versionStack = null;
    Map<String, Object> attributes = new HashMap<>();
    try {
      data = CRC.extractDataWithCRC(data, offset);
      MutableOffset dataOffset = new MutableOffset();
      versionStack =
          new VersionStack((ArrayList<String>) Serializer.deserializeObject(data, dataOffset));
      int attributeCount = Serializer.deserializeIntValue(data, dataOffset);
      for (int i = 0; i < attributeCount; i++) {
        String attributeName = Serializer.deserializeString(data, dataOffset);
        String type = Serializer.deserializeString(data, dataOffset);
        if (type == null) {
          throw new RuntimeException();
        }
        switch (type) {
          case "String":
            attributes.put(attributeName, Serializer.deserializeString(data, dataOffset));
            break;

          case "Boolean":
            attributes.put(attributeName, Serializer.deserializeBoolean(data, dataOffset));
            break;

          case "Byte":
            attributes.put(attributeName, Serializer.deserializeByte(data, dataOffset));
            break;

          case "Short":
            attributes.put(attributeName, Serializer.deserializeShort(data, dataOffset));
            break;

          case "Integer":
            attributes.put(attributeName, Serializer.deserializeInt(data, dataOffset));
            break;

          case "Long":
            attributes.put(attributeName, Serializer.deserializeLong(data, dataOffset));
            break;

          case "Float":
            attributes.put(attributeName, Serializer.deserializeFloat(data, dataOffset));
            break;

          case "Double":
            attributes.put(attributeName, Serializer.deserializeDouble(data, dataOffset));
            break;

          case "Enum":
            String enumType = Serializer.deserializeString(data, dataOffset);
            Class enumClass = Class.forName(enumType);
            attributes.put(attributeName, Serializer.deserializeEnum(enumClass, data, dataOffset));
            break;

          case "ByteArray":
            attributes.put(attributeName, Serializer.deserializeBytes(data, dataOffset));
            break;

          case "Serializable":
            attributes.put(attributeName, Serializer.deserializeObject(data, dataOffset));
            break;

          default:
            // the VersionedObjectSerializer failed when deserializing the byte array
            throw new RuntimeException("Unexpected type: " + type);
        }
      }
      versionedObject.deserialize(versionStack.retrieveVersion(), attributes, versionStack);
    } catch (RuntimeException e) {
      throw new VersionedSerializationException(
          versionStack, attributes, VersionedSerializationException.Reason.INCORRECT_DATA);
    } catch (UnrecognizedVersionException e) {
      throw new VersionedSerializationException(
          versionStack, attributes, VersionedSerializationException.Reason.UNRECOGNIZED_VERSION);
    } catch (ClassNotFoundException e) {
      throw new VersionedSerializationException(
          versionStack, attributes, VersionedSerializationException.Reason.CLASS_NOT_FOUND);
    } catch (CRCMismatchException e) {
      throw new VersionedSerializationException(
          null, attributes, VersionedSerializationException.Reason.CRC_MISMATCH);
    }
  }