@SuppressWarnings("unchecked")
    public <T> T readValue(ClickHouseColumn column, Class<?> typeHint) throws IOException {
        if (column.isNullable()) {
            int isNull = readByteOrEOF(input);
            if (isNull == 1) { // is Null?
                return (T) null;
            }
        }

        ClickHouseColumn actualColumn = column.getDataType() == ClickHouseDataType.Dynamic ? readDynamicData() : column;
        ClickHouseDataType dataType = actualColumn.getDataType();
        int precision = actualColumn.getPrecision();
        int scale = actualColumn.getScale();
        TimeZone timezone = actualColumn.getTimeZoneOrDefault(timeZone);

        try {
            switch (dataType) {
                // Primitives
                case FixedString: {
                    byte[] bytes = precision > STRING_BUFF.length ?
                            new byte[precision] : STRING_BUFF;
                    readNBytes(input, bytes, 0, precision);
                    return (T) new String(bytes, 0, precision, StandardCharsets.UTF_8);
                }
                case String: {
                    return (T) readString();
                }
                case Int8:
                    return (T) Byte.valueOf(readByte());
                case UInt8:
                    return (T) Short.valueOf(readUnsignedByte());
                case Int16:
                    return (T) (Short)readShortLE();
                case UInt16:
                    return (T) (Integer)readUnsignedShortLE();
                case Int32:
                    return (T) (Integer)readIntLE();
                case UInt32:
                    return (T) (Long)(readUnsignedIntLE());
                case Int64:
                    return (T) (Long)(readLongLE());
                case UInt64:
                    return (T) readBigIntegerLE(INT64_SIZE, true);
                case Int128:
                    return (T) readBigIntegerLE(INT128_SIZE, false);
                case UInt128:
                    return (T) readBigIntegerLE(INT128_SIZE, true);
                case Int256:
                    return (T) readBigIntegerLE(INT256_SIZE, false);
                case UInt256:
                    return (T) readBigIntegerLE(INT256_SIZE, true);
                case Decimal:
                    return (T) readDecimal(precision, scale);
                case Decimal32:
                    return (T) readDecimal(ClickHouseDataType.Decimal32.getMaxPrecision(), scale);
                case Decimal64:
                    return (T) readDecimal(ClickHouseDataType.Decimal64.getMaxPrecision(), scale);
                case Decimal128:
                    return (T) readDecimal(ClickHouseDataType.Decimal128.getMaxPrecision(), scale);
                case Decimal256:
                    return (T) readDecimal(ClickHouseDataType.Decimal256.getMaxPrecision(), scale);
                case Float32:
                    return (T) (Float)readFloatLE();
                case Float64:
                    return (T) (Double)readDoubleLE();
                case Bool:
                    return (T) Boolean.valueOf(readByteOrEOF(input) == 1);
                case Enum8: {
                    byte enum8Val = (byte) readUnsignedByte();
                    String name = actualColumn.getEnumConstants().nameNullable(enum8Val);
                    return (T) new EnumValue(name == null ? "<unknown>" : name, enum8Val);
                }
                case Enum16: {
                    short enum16Val = (short) readUnsignedShortLE();
                    String name = actualColumn.getEnumConstants().nameNullable(enum16Val);
                    return (T) new EnumValue(name == null ? "<unknown>" : name, enum16Val);
                }
                case Date:
                    return convertDateTime(readDate(timezone), typeHint);
                case Date32:
                    return convertDateTime(readDate32(timezone), typeHint);
                case DateTime:
                    return convertDateTime(readDateTime32(timezone), typeHint);
                case DateTime32:
                    return convertDateTime(readDateTime32(timezone), typeHint);
                case DateTime64:
                    return convertDateTime(readDateTime64(scale, timezone), typeHint);
                case IntervalYear:
                case IntervalQuarter:
                case IntervalMonth:
                case IntervalWeek:
                case IntervalDay:
                case IntervalHour:
                case IntervalMinute:
                case IntervalSecond:
                case IntervalMicrosecond:
                case IntervalMillisecond:
                case IntervalNanosecond:
                    return (T) readIntervalValue(dataType, input);
                case IPv4:
                    // https://clickhouse.com/docs/en/sql-reference/data-types/ipv4
                    return (T) Inet4Address.getByAddress(readNBytesLE(input, 4));
                case IPv6:
                    // https://clickhouse.com/docs/en/sql-reference/data-types/ipv6
                    return (T) Inet6Address.getByAddress(readNBytes(input, 16));
                case UUID:
                    return (T) new UUID(readLongLE(), readLongLE());
                case Point:
                    return (T) readGeoPoint();
                case Polygon:
                    return (T) readGeoPolygon();
                case MultiPolygon:
                    return (T) readGeoMultiPolygon();
                case MultiLineString:
                    return (T) readGeoPolygon();
                case Ring:
                    return (T) readGeoRing();
                case LineString:
                    return (T) readGeoRing();
                case JSON: // experimental https://clickhouse.com/docs/en/sql-reference/data-types/newjson
                    if (jsonAsString) {
                        return (T) readString(input);
                    } else {
                        return (T) readJsonData(input);
                    }
//                case Object: // deprecated https://clickhouse.com/docs/en/sql-reference/data-types/object-data-type
                case Array:
                    return convertArray(readArray(actualColumn), typeHint);
                case Map:
                    return (T) readMap(actualColumn);
                case Tuple:
                    return (T) readTuple(actualColumn);
                case Nothing:
                    return null;
                case SimpleAggregateFunction:
                    return (T) readValue(column.getNestedColumns().get(0));
                case AggregateFunction:
                    return (T) readBitmap( actualColumn);
                case Variant:
                    return (T) readVariant(actualColumn);
                case Dynamic:
                    return (T) readValue(actualColumn, typeHint);
                case Nested:
                    return convertArray(readNested(actualColumn), typeHint);
                default:
                    throw new IllegalArgumentException("Unsupported data type: " + actualColumn.getDataType());
            }
        } catch (EOFException e) {
            throw e;
        } catch (Exception e) {
            log.debug("Failed to read value for column {}, {}", column.getColumnName(), e.getLocalizedMessage());
            throw new ClientException("Failed to read value for column " + column.getColumnName(), e);
        }
    }
Reads a value from the internal input stream. Method will use type hint to do smarter conversion if possible.
For example, all datetime values are of {@link ZonedDateTime}; if a type hint is {@link LocalDateTime} then
{@link ZonedDateTime#toLocalDateTime()} .
@param column - column information
@param typeHint - type hint
@return value
@param <T> - target type of the value
@throws IOException when IO error occurs
client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryStreamReader.java