【问题标题】:How to avoid casting while extracting fields from GenericRecord in avro?如何在avro中从GenericRecord中提取字段时避免强制转换?
【发布时间】:2017-05-09 11:51:34
【问题描述】:

我正在使用 Avro,我正在尝试以通用方式从中提取几个字段。我有一个来自 avro 的 GenericRecord 对象,我想从中提取一些字段。

下面是做所有事情的方法:

  public static Object extract(GenericRecord genericRecord, String fieldName) {
    Object result = new Object();
    for (Field field : genericRecord.getSchema().getFields()) {
      if (field.name().equalsIgnoreCase(fieldName))
        return fromAvro(genericRecord.get(field.name()), field.schema());
    }
    return result;
  }

  private static Object fromAvro(Object obj, Schema schema) {
    if (obj == null)
      return null;
    switch (schema.getType()) {
      case UNION:
        return fromAvroUnion(obj, schema);
      case ARRAY:
        return fromAvroArray(obj, schema);
      case STRING:
        if (obj.equals("null") || obj.toString().equals("null"))
          return null;
        else
          return obj.toString();
      case MAP:
        return fromAvroMap(obj, schema);
      case NULL:
        return obj;
      case BOOLEAN:
        return Boolean.valueOf(obj.toString());
      case DOUBLE:
        return Double.valueOf(obj.toString());
      case FLOAT:
        return Float.valueOf(obj.toString());
      case INT:
        return Integer.valueOf(obj.toString());
      case LONG:
        return Long.valueOf(obj.toString());
    }
  }

  private static Object fromAvroArray(Object obj, Schema schema) {
    List<Object> array = new ArrayList<Object>();
    for (Object element : (GenericData.Array) obj) {
      array.add(fromAvro(element, schema.getElementType()));
    }
    return array;
  }

  private static Object fromAvroMap(Object obj, Schema schema) {
    Map<String, Object> convertedMap = new HashMap<>();
    // CharSequence because the string can be configured as either Utf8 or String.
    for (Entry<CharSequence, Object> e : ((Map<CharSequence, Object>) obj).entrySet()) {
      convertedMap.put(e.getKey().toString(), fromAvro(e.getValue(), schema.getValueType()));
    }
    return convertedMap;
  }

  private static Object fromAvroUnion(Object obj, Schema schema) {
    List<Schema> types = schema.getTypes();
    if (types.size() < 1) {
      throw new AvroRuntimeException("Union has no types");
    }
    if (types.size() == 1) {
      return fromAvro(obj, types.get(0));
    } else if (types.size() > 2) {
      throw new AvroRuntimeException(
          "Unions may only consist of a concrete type and null in cascading.avro");
    } else if (!types.get(0).getType().equals(Type.NULL)
        && !types.get(1).getType().equals(Type.NULL)) {
      throw new AvroRuntimeException(
          "Unions may only consist of a concrete type and null in cascading.avro");
    } else {
      Integer concreteIndex = (types.get(0).getType() == Type.NULL) ? 1 : 0;
      return fromAvro(obj, types.get(concreteIndex));
    }
  }

以下是我如何使用上述extract 方法从GenericRecord 对象中提取单个字段。现在我只是在提取 String Integer 数据类型字段,但我可能还需要提取其他数据类型字段。

现在我的问题是。有什么方法可以避免强制转换,因为现在我在从中提取字段的同时强制转换每种数据类型。我认为只要有演员表,就必须有更好的方法来做可以避免演员表的事情。

Map<String, String> payload = (Map<String, String>) extract(genericRecord, "payload");
String clientId = (String) extract(genericRecord, "clientId");
Integer deviceId = (Integer) extract(genericRecord, "deviceId");

【问题讨论】:

  • 为什么不使用SpecificRecord

标签: java casting type-conversion avro


【解决方案1】:

现在我的问题是。有什么办法可以避免铸造,因为对 现在我正在转换每种数据类型,同时从中提取字段。一世 认为只要有演员表,就必须有更好的做事方式 可以避免强制转换。

您使用非常通用的方法,因为来自 Avro 的 GenericRecord 是一个非常通用的映射(它使用 Object 作为键和 Object 作为值)。
在您的情况下,不幸的是,一次或另一次强制转换是不可避免的,因为您使用的是非常弱类型的数据:Object class。
现在,您可以在实用程序类中而不是在客户端中创建它。 它会使工作不那么痛苦,但在运行时不会更安全。

您可以通过这种方式更改extract() 方法:

   @SuppressWarnings("unchecked")
   public static <T> T extract(GenericRecord genericRecord, String fieldName) {
    for (Field field : genericRecord.getSchema().getFields()) {
      if (field.name().equalsIgnoreCase(fieldName))
        return (T) fromAvro(genericRecord.get(field.name()), field.schema());
    }
    return null;
   }

现在在客户端你可以这样做:

Map<String, String> payload = extract(genericRecord, "payload");
String clientId = extract(genericRecord, "clientId");
Integer deviceId = extract(genericRecord, "deviceId");

【讨论】:

  • 它不工作。我试着改变你的建议。它抱怨所有 switch 语句的情况。字符串大小写的一个示例错误是Type mismatch: cannot convert from String to T
  • 确实如此。您还应该将您调用的所有方法的返回值以及在switch 中返回的非空值强制转换为(T)。我编辑来说明。我还在方法上添加了@SuppressWarnings("unchecked")
  • 我也必须更改我的extract 方法吗?正确的?因为到目前为止它给了我一个错误。
  • 我更新了我的答案。最后,只需要在更高级的调用中引入T泛型,即在extract()方法中,并将fromAvro()方法的返回值强制转换为T。此外,我认为 Object result 变量不是必需的,因为如果架构中没有匹配的字段,您应该返回一个 null 值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多