void read(
    Protocol_* iprot,
    const TypeInfo& typeInfo,
    ProtocolReaderStructReadState<Protocol_>& readState,
    void* object) {
  using WireTypeInfo = ProtocolReaderWireTypeInfo<Protocol_>;
  using WireType = typename WireTypeInfo::WireType;
  switch (typeInfo.type) {
    case protocol::TType::T_STRUCT:
      readState.beforeSubobject(iprot);
      read<Protocol_>(
          iprot,
          *static_cast<const StructInfo*>(typeInfo.typeExt),
          typeInfo.set ? invokeSet(typeInfo.set, object) : object);
      readState.afterSubobject(iprot);
      break;
    case protocol::TType::T_I64: {
      std::int64_t temp;
      iprot->readI64(temp);
      reinterpret_cast<void (*)(void*, std::int64_t)>(typeInfo.set)(
          object, temp);
      break;
    }
    case protocol::TType::T_I32: {
      std::int32_t temp;
      iprot->readI32(temp);
      reinterpret_cast<void (*)(void*, std::int32_t)>(typeInfo.set)(
          object, temp);
      break;
    }
    case protocol::TType::T_I16: {
      std::int16_t temp;
      iprot->readI16(temp);
      reinterpret_cast<void (*)(void*, std::int16_t)>(typeInfo.set)(
          object, temp);
      break;
    }
    case protocol::TType::T_BYTE: {
      std::int8_t temp;
      iprot->readByte(temp);
      reinterpret_cast<void (*)(void*, std::int8_t)>(typeInfo.set)(
          object, temp);
      break;
    }
    case protocol::TType::T_BOOL: {
      bool temp;
      iprot->readBool(temp);
      reinterpret_cast<void (*)(void*, bool)>(typeInfo.set)(object, temp);
      break;
    }
    case protocol::TType::T_DOUBLE: {
      double temp;
      iprot->readDouble(temp);
      reinterpret_cast<void (*)(void*, double)>(typeInfo.set)(object, temp);
      break;
    }
    case protocol::TType::T_FLOAT: {
      float temp;
      iprot->readFloat(temp);
      reinterpret_cast<void (*)(void*, float)>(typeInfo.set)(object, temp);
      break;
    }
    case protocol::TType::T_STRING: {
      switch (*static_cast<const StringFieldType*>(typeInfo.typeExt)) {
        case StringFieldType::String:
          iprot->readString(*static_cast<std::string*>(object));
          break;
        case StringFieldType::IOBuf:
          iprot->readBinary(*static_cast<folly::IOBuf*>(object));
          break;
        case StringFieldType::IOBufPtr:
          iprot->readBinary(
              *static_cast<std::unique_ptr<folly::IOBuf>*>(object));
          break;
      }
      break;
    }
    case protocol::TType::T_MAP: {
      readState.beforeSubobject(iprot);
      // Initialize the container to clear out current values.
      auto* actualObject = invokeSet(typeInfo.set, object);
      const MapFieldExt& ext =
          *static_cast<const MapFieldExt*>(typeInfo.typeExt);
      std::uint32_t size = ~0;
      WireType reportedKeyType = WireTypeInfo::defaultValue();
      WireType reportedMappedType = WireTypeInfo::defaultValue();
      iprot->readMapBegin(reportedKeyType, reportedMappedType, size);
      struct Context {
        const TypeInfo* keyInfo;
        const TypeInfo* valInfo;
        Protocol_* iprot;
        ProtocolReaderStructReadState<Protocol_>& readState;
      };
      const Context context = {
          ext.keyInfo,
          ext.valInfo,
          iprot,
          readState,
      };
      auto const keyReader = [](const void* context, void* key) {
        const auto& typedContext = *static_cast<const Context*>(context);
        read(
            typedContext.iprot,
            *typedContext.keyInfo,
            typedContext.readState,
            key);
      };
      auto const valueReader = [](const void* context, void* val) {
        const auto& typedContext = *static_cast<const Context*>(context);
        read(
            typedContext.iprot,
            *typedContext.valInfo,
            typedContext.readState,
            val);
      };
      if (iprot->kOmitsContainerSizes()) {
        while (iprot->peekMap()) {
          ext.consumeElem(&context, actualObject, keyReader, valueReader);
        }
      } else {
        if (size > 0 &&
            (ext.keyInfo->type != reportedKeyType ||
             ext.valInfo->type != reportedMappedType)) {
          skip_n(*iprot, size, {reportedKeyType, reportedMappedType});
        } else {
          if (!canReadNElements(
                  *iprot, size, {reportedKeyType, reportedMappedType})) {
            protocol::TProtocolException::throwTruncatedData();
          }
          ext.readMap(&context, actualObject, size, keyReader, valueReader);
        }
      }
      iprot->readMapEnd();
      readState.afterSubobject(iprot);
      break;
    }
    case protocol::TType::T_SET: {
      readState.beforeSubobject(iprot);
      // Initialize the container to clear out current values.
      auto* actualObject = invokeSet(typeInfo.set, object);
      const SetFieldExt& ext =
          *static_cast<const SetFieldExt*>(typeInfo.typeExt);
      std::uint32_t size = ~0;
      WireType reportedType = WireTypeInfo::defaultValue();
      iprot->readSetBegin(reportedType, size);
      struct Context {
        const TypeInfo* valInfo;
        Protocol_* iprot;
        ProtocolReaderStructReadState<Protocol_>& readState;
      };
      const Context context = {
          ext.valInfo,
          iprot,
          readState,
      };
      auto const reader = [](const void* context, void* value) {
        const auto& typedContext = *static_cast<const Context*>(context);
        read(
            typedContext.iprot,
            *typedContext.valInfo,
            typedContext.readState,
            value);
      };
      if (iprot->kOmitsContainerSizes()) {
        while (iprot->peekSet()) {
          ext.consumeElem(&context, actualObject, reader);
        }
      } else {
        if (reportedType != ext.valInfo->type) {
          skip_n(*iprot, size, {reportedType});
        } else {
          if (!canReadNElements(*iprot, size, {reportedType})) {
            protocol::TProtocolException::throwTruncatedData();
          }
          ext.readSet(&context, actualObject, size, reader);
        }
      }
      iprot->readSetEnd();
      readState.afterSubobject(iprot);
      break;
    }
    case protocol::TType::T_LIST: {
      readState.beforeSubobject(iprot);
      // Initialize the container to clear out current values.
      auto* actualObject = invokeSet(typeInfo.set, object);
      const ListFieldExt& ext =
          *static_cast<const ListFieldExt*>(typeInfo.typeExt);
      std::uint32_t size = ~0;
      WireType reportedType = WireTypeInfo::defaultValue();

      iprot->readListBegin(reportedType, size);
      struct Context {
        const TypeInfo* valInfo;
        Protocol_* iprot;
        ProtocolReaderStructReadState<Protocol_>& readState;
      };
      const Context context = {
          ext.valInfo,
          iprot,
          readState,
      };
      auto const reader = [](const void* context, void* value) {
        const auto& typedContext = *static_cast<const Context*>(context);
        read(
            typedContext.iprot,
            *typedContext.valInfo,
            typedContext.readState,
            value);
      };
      if (iprot->kOmitsContainerSizes()) {
        while (iprot->peekList()) {
          ext.consumeElem(&context, actualObject, reader);
        }
      } else {
        if (reportedType != ext.valInfo->type) {
          skip_n(*iprot, size, {reportedType});
        } else {
          if (!canReadNElements(*iprot, size, {reportedType})) {
            protocol::TProtocolException::throwTruncatedData();
          }
          ext.readList(&context, actualObject, size, reader);
        }
      }
      iprot->readListEnd();
      readState.afterSubobject(iprot);
      break;
    }
    case protocol::TType::T_STOP:
    case protocol::TType::T_VOID:
    case protocol::TType::T_UTF8:
    case protocol::TType::T_U64:
    case protocol::TType::T_UTF16:
    case protocol::TType::T_STREAM:
      skip(iprot, readState);
  }
}