public static byte[] serialize(VersionedObject versionedObject, int CRCBytes)
     throws NotSerializableException {
   FragmentedByteArray data =
       new FragmentedByteArray(
           Serializer.serializeObject(versionedObject.getCurrentVersion().toArrayList()));
   Map<String, Serializable> attributes = versionedObject.serialize();
   data.add(Serializer.serialize(attributes.size()));
   for (Map.Entry<String, Serializable> entry : attributes.entrySet()) {
     byte[] attributeName = Serializer.serialize(entry.getKey());
     // find the type of the attributes
     byte[] type;
     byte[] attributeArray;
     Object attribute = entry.getValue();
     if (attribute instanceof String) {
       type = STRING_TYPE;
       attributeArray = Serializer.serialize((String) attribute);
     } else if (attribute instanceof Boolean || attribute == null) {
       // null value are serialized as a null Boolean
       type = BOOLEAN_TYPE;
       attributeArray = Serializer.serialize((Boolean) attribute);
     } else if (attribute instanceof Byte) {
       type = BYTE_TYPE;
       attributeArray = Serializer.serialize((Byte) attribute);
     } else if (attribute instanceof Short) {
       type = SHORT_TYPE;
       attributeArray = Serializer.serialize((Short) attribute);
     } else if (attribute instanceof Integer) {
       type = INTEGER_TYPE;
       attributeArray = Serializer.serialize((Integer) attribute);
     } else if (attribute instanceof Long) {
       type = LONG_TYPE;
       attributeArray = Serializer.serialize((Long) attribute);
     } else if (attribute instanceof Float) {
       type = FLOAT_TYPE;
       attributeArray = Serializer.serialize((Float) attribute);
     } else if (attribute instanceof Double) {
       type = DOUBLE_TYPE;
       attributeArray = Serializer.serialize((Double) attribute);
     } else if (attribute instanceof Enum<?>) {
       type = ENUM_TYPE;
       attributeArray =
           Serializer.addArrays(
               Serializer.serialize(attribute.getClass().getName()),
               Serializer.serialize((Enum) attribute));
     } else if (attribute instanceof byte[]) {
       type = BYTE_ARRAY_TYPE;
       attributeArray = Serializer.serialize((byte[]) attribute);
     } else {
       type = SERIALIZABLE_TYPE;
       attributeArray = Serializer.serializeObject((Serializable) attribute);
     }
     data.add(attributeName, type, attributeArray).generateArray();
   }
   return CRC.addCRC(data.generateArray(), CRCBytes, true);
 }
  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);
    }
  }