docs: private static Object fixValue(TypeSignature signature, Object value)
      {
          if (value == null) {
              return null;
          }
  
          if (signature.getBase().equals(ARRAY)) {
              List<Object> fixedValue = new ArrayList<>();
              for (Object object : List.class.cast(value)) {
                  fixedValue.add(fixValue(signature.getTypeParametersAsTypeSignatures().get(0), object));
              }
              return fixedValue;
          }
          if (signature.getBase().equals(MAP)) {
              TypeSignature keySignature = signature.getTypeParametersAsTypeSignatures().get(0);
              TypeSignature valueSignature = signature.getTypeParametersAsTypeSignatures().get(1);
              Map<Object, Object> fixedValue = new HashMap<>();
              for (Map.Entry<?, ?> entry : (Set<Map.Entry<?, ?>>) Map.class.cast(value).entrySet()) {
                  fixedValue.put(fixValue(keySignature, entry.getKey()), fixValue(valueSignature, entry.getValue()));
              }
              return fixedValue;
          }
          if (signature.getBase().equals(ROW)) {
              Map<String, Object> fixedValue = new LinkedHashMap<>();
              List<Object> listValue = List.class.cast(value);
              checkArgument(listValue.size() == signature.getParameters().size(), "Mismatched data values and row type");
              for (int i = 0; i < listValue.size(); i++) {
                  TypeSignatureParameter parameter = signature.getParameters().get(i);
                  checkArgument(
                          parameter.getKind() == ParameterKind.NAMED_TYPE,
                          "Unexpected parameter [%s] for row type",
                          parameter);
                  NamedTypeSignature namedTypeSignature = parameter.getNamedTypeSignature();
                  String key = namedTypeSignature.getName().orElse("field" + i);
                  fixedValue.put(key, fixValue(namedTypeSignature.getTypeSignature(), listValue.get(i)));
              }
              return fixedValue;
          }
          switch (signature.getBase()) {
              case BIGINT:
                  if (value instanceof String) {
                      return Long.parseLong((String) value);
                  }
                  return ((Number) value).longValue();
              case INTEGER:
                  if (value instanceof String) {
                      return Integer.parseInt((String) value);
                  }
                  return ((Number) value).intValue();
              case SMALLINT:
                  if (value instanceof String) {
                      return Short.parseShort((String) value);
                  }
                  return ((Number) value).shortValue();
              case TINYINT:
                  if (value instanceof String) {
                      return Byte.parseByte((String) value);
                  }
                  return ((Number) value).byteValue();
              case DOUBLE:
                  if (value instanceof String) {
                      return Double.parseDouble((String) value);
                  }
                  return ((Number) value).doubleValue();
              case REAL:
                  if (value instanceof String) {
                      return Float.parseFloat((String) value);
                  }
                  return ((Number) value).floatValue();
              case BOOLEAN:
                  if (value instanceof String) {
                      return Boolean.parseBoolean((String) value);
                  }
                  return Boolean.class.cast(value);
              case VARCHAR:
              case JSON:
              case TIME:
              case TIME_WITH_TIME_ZONE:
              case TIMESTAMP:
              case TIMESTAMP_WITH_TIME_ZONE:
              case DATE:
              case INTERVAL_YEAR_TO_MONTH:
              case INTERVAL_DAY_TO_SECOND:
              case IPADDRESS:
              case DECIMAL:
              case CHAR:
              case GEOMETRY:
                  return String.class.cast(value);
              case BING_TILE:
                  // Bing tiles are serialized as strings when used as map keys,
                  // they are serialized as json otherwise (value will be a LinkedHashMap).
                  return value;
              default:
                  // for now we assume that only the explicit types above are passed
                  // as a plain text and everything else is base64 encoded binary
                  if (value instanceof String) {
                      return Base64.getDecoder().decode((String) value);
                  }
                  return value;
          }
      }