2

是否有一种机制可以应用一组标准检查来检测字符串,然后将其转换为检测到的类型,使用 Jackson 的标准文本相关库(csv、json 甚至 jackson-core)之一?我可以想象将它与与该值关联的标签(例如 CSV 标头)一起使用来执行类似以下的操作:

JavaTypeAndValue typeAndValue = StringToJavaType.fromValue(Object x, String label);  
typeAndValue.type() // FQN of Java type, maybe
typeAndValue.label() // where label might be a column header value, for example
typeAndValue.value() // returns Object  of typeAndValue.type()

需要一组“提取器”来应用转换,并且类的消费者必须意识到“对象”返回类型的“歧义”,但仍然能够消费和使用信息,因为它目的。

我目前正在考虑的示例涉及构建 SQL DDL 或 DML,例如使用从评估 csv 文件中的行派生的 List 中的信息的 CREATE Table 语句。

经过更多的挖掘,希望在那里找到一些东西,我写下了我的想法的开始。

请记住,我在这里的意图不是要呈现“完整”的东西,因为我确信这里缺少一些东西,没有解决边缘情况等。

例如,这pasrse(List<Map<String, String>> rows, List<String> headers源于这样的想法,即这可能是从杰克逊读取的 CSV 文件中的行样本。

同样,这并不完整,所以我不想挑出以下所有问题。问题不是“我们将如何写这个?”,而是“有没有人熟悉存在的类似以下内容的东西?”。

import gms.labs.cassandra.sandbox.extractors.Extractor;
import gms.labs.cassandra.sandbox.extractors.Extractors;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

@Accessors(fluent=true, chain=true)
public class TypeAndValue
{

    @Builder
    TypeAndValue(Class<?> type, String rawValue){
        this.type = type;
        this.rawValue = rawValue;
        label = "NONE";
    }

    @Getter
    final Class<?> type;

    @Getter
    final String rawValue;

    @Setter
    @Getter
    String label;

    public Object value(){
        return Extractors.extractorFor(this).value(rawValue);
    }

    static final String DEFAULT_LABEL = "NONE";

}

一个简单的解析器,它parse来自我List<Map<String,String>>从 CSVReader 获得的上下文。

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.math.NumberUtils;

import java.util.*;
import java.util.function.BiFunction;

public class JavaTypeParser
{
public static final List<TypeAndValue> parse(List<Map<String, String>> rows, List<String> headers)
{
    List<TypeAndValue> typesAndVals = new ArrayList<TypeAndValue>();
    for (Map<String, String> row : rows) {
        for (String header : headers) {
            String val = row.get(header);
            TypeAndValue typeAndValue =
                    //  isNull, isBoolean, isNumber
                    isNull(val).orElse(isBoolean(val).orElse(isNumber(val).orElse(_typeAndValue.apply(String.class, val).get())));
            typesAndVals.add(typeAndValue.label(header));
        }
    }
  
}

public static Optional<TypeAndValue> isNumber(String val)
{
    if (!NumberUtils.isCreatable(val)) {
        return Optional.empty();
    } else {
        return _typeAndValue.apply(NumberUtils.createNumber(val).getClass(), val);
    }
}

public static Optional<TypeAndValue> isBoolean(String val)
{
    boolean bool = (val.equalsIgnoreCase("true") || val.equalsIgnoreCase("false"));
    if (bool) {
        return _typeAndValue.apply(Boolean.class, val);
    } else {
        return Optional.empty();
    }
}

public static Optional<TypeAndValue> isNull(String val){
    if(Objects.isNull(val) || val.equals("null")){
        return _typeAndValue.apply(ObjectUtils.Null.class,val);
    }
    else{
        return Optional.empty();
    }
}

static final BiFunction<Class<?>, String, Optional<TypeAndValue>> _typeAndValue = (type, value) -> Optional.of(
        TypeAndValue.builder().type(type).rawValue(value).build());

}

提取器。只是一个示例,说明如何在某处注册值的“提取器”(包含在字符串中)以进行查找。它们也可以通过任何其他方式引用。

import gms.labs.cassandra.sandbox.TypeAndValue;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.math.NumberUtils;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;

public class Extractors
{

private static final List<Class> NUMS = Arrays.asList(
        BigInteger.class,
        BigDecimal.class,
        Long.class,
        Integer.class,
        Double.class,
        Float.class);

public static final Extractor<?> extractorFor(TypeAndValue typeAndValue)
{
    if (NUMS.contains(typeAndValue.type())) {
        return (Extractor<Number>) value -> NumberUtils.createNumber(value);
    } else if(typeAndValue.type().equals(Boolean.class)) {
        return  (Extractor<Boolean>) value -> Boolean.valueOf(value);
    } else if(typeAndValue.type().equals(ObjectUtils.Null.class)) {
        return  (Extractor<ObjectUtils.Null>) value -> null; // should we just return the raw value.  some frameworks coerce to null.
    } else if(typeAndValue.type().equals(String.class)) {
        return  (Extractor<String>) value -> typeAndValue.rawValue(); // just return the raw value.  some frameworks coerce to null.
    }
    else{
        throw new RuntimeException("unsupported");
    }
}
}

我从 JavaTypeParser 类中运行它,以供参考。

public static void main(String[] args)
{

    Optional<TypeAndValue> num = isNumber("-1230980980980980980980980980980988009808989080989809890808098292");
    num.ifPresent(typeAndVal -> {
        System.out.println(typeAndVal.value());
        System.out.println(typeAndVal.value().getClass());  // BigInteger
    });
    num = isNumber("-123098098097987");
    num.ifPresent(typeAndVal -> {
        System.out.println(typeAndVal.value());
        System.out.println(typeAndVal.value().getClass()); // Long
    });
    num = isNumber("-123098.098097987"); // Double
    num.ifPresent(typeAndVal -> {
        System.out.println(typeAndVal.value());
        System.out.println(typeAndVal.value().getClass());
    });
    num = isNumber("-123009809890898.0980979098098908080987"); // BigDecimal
    num.ifPresent(typeAndVal -> {
        System.out.println(typeAndVal.value());
        System.out.println(typeAndVal.value().getClass());
    });

    Optional<TypeAndValue> bool = isBoolean("FaLse");
    bool.ifPresent(typeAndVal -> {
        System.out.println(typeAndVal.value());
        System.out.println(typeAndVal.value().getClass()); // Boolean
    });

    Optional<TypeAndValue> nulll = isNull("null");
    nulll.ifPresent(typeAndVal -> {
        System.out.println(typeAndVal.value());
        //System.out.println(typeAndVal.value().getClass());  would throw null pointer exception
        System.out.println(typeAndVal.type()); // ObjectUtils.Null (from apache commons lang3)
    });

}
4

3 回答 3

3

我不知道有任何库可以做到这一点,也从未见过在一组开放的可能类型上以这种方式工作的任何东西。

对于封闭的类型集(您知道所有可能的输出类型),更简单的方法是将类 FQN 写入字符串中(根据您的描述,如果您控制写入的字符串,我没有得到)。
完整的 FQN或它的别名

否则我认为不写所有支票是没有办法的。

此外,当我考虑边缘用例时,它会非常微妙。

假设您在字符串中使用 json 作为序列化格式,您将如何区分String类似的值Hello WorldDate以某种 ISO 格式(例如 . 2020-09-22)编写的值。为此,您需要在您所做的检查中引入一些优先级(首先尝试使用一些正则表达式检查它是否是一个日期,如果不是,则使用下一个并且简单的字符串一个是最后一个)

如果你有两个对象怎么办:

   String name;
   String surname;
}

class Employee {
   String name;
   String surname;
   Integer salary
}

您会收到第二种类型的序列化值,但薪水为空(null 或该属性完全缺失)。

您如何区分集合或列表?

我不知道您的意图是否如此动态,或者您已经知道所有可能的可反序列化类型,也许问题中的更多细节会有所帮助。

更新

刚看到代码,现在看起来更清楚了。如果您知道所有可能的输出,那就是这样。
我要做的唯一更改是减轻您想要管理抽象提取过程的类型的增加。
为此,我认为应该做一个小的改变,比如:

interface Extractor {
    Boolean match(String value);
    Object extract(String value);
}

然后您可以为每种类型定义一个提取器:

class NumberExtractor implements Extractor<T> {
    public Boolean match(String val) {
        return NumberUtils.isCreatable(val);
    }
    public Object extract(String value) {
        return NumberUtils.createNumber(value);
    }
}
class StringExtractor implements Extractor {
    public Boolean match(String s) {
        return true; //<-- catch all
    }
    public Object extract(String value) {
        return value;
    }
}

然后注册并自动化检查:

public class JavaTypeParser {
  private static final List<Extractor> EXTRACTORS = List.of(
      new NullExtractor(),
      new BooleanExtractor(),
      new NumberExtractor(),
      new StringExtractor()
  )

  public static final List<TypeAndValue> parse(List<Map<String, String>> rows, List<String> headers) {
    List<TypeAndValue> typesAndVals = new ArrayList<TypeAndValue>();
    for (Map<String, String> row : rows) {
        for (String header : headers) {
            String val = row.get(header);
            
            typesAndVals.add(extract(header, val));
        }
    }
}
  public static final TypeAndValue extract(String header, String value) {
       for (Extractor<?> e : EXTRACTOR) {
           if (e.match(value) {
               Object v = extractor.extract(value);
               return TypeAndValue.builder()
                         .label(header)
                         .value(v) //<-- you can put the real value here, and remove the type field
                         .build()
           }
       }
       throw new IllegalStateException("Can't find an extractor for: " + header + " | " + value);

  }

要解析 CSV,我建议使用https://commons.apache.org/proper/commons-csv,因为 CSV 解析可能会引发令人讨厌的问题。

于 2020-09-22T20:51:38.243 回答
2

你真正想做的是写一个parser。您将片段翻译成解析树。解析树捕获类型和值。对于像数组和对象这样的分层类型,每个树节点都包含子节点。

Antlr是最常用的解析器之一(尽管对您的用例来说有点矫枉过正)。Antlr 为Json带来了开箱即用的支持。

我建议花时间吸收所有涉及的概念。尽管一开始它可能看起来有点矫枉过正,但当您进行任何类型的扩展时,它很快就会得到回报。改变语法相对容易;生成的代码相当复杂。此外,所有解析器生成器都会验证您的语法以显示逻辑错误。

当然,如果您限制自己仅解析 CSV 或 JSON(而不是同时解析两者),您应该使用现有库的解析器。例如,jackson 有ObjectMapper.readTree来获取解析树。您还可以使用ObjectMapper.readValue(<fragment>, Object.class)简单地获取规范的 java 类。

于 2020-09-29T06:57:33.787 回答
0

尝试这个 :

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

String j = // json string;

            JsonFactory jsonFactory = new JsonFactory();
            ObjectMapper jsonMapper = new ObjectMapper(jsonFactory);
            JsonNode jsonRootNode = jsonMapper.readTree(j);
            Iterator<Map.Entry<String,JsonNode>> jsonIterator = jsonRootNode.fields();

            while (jsonIterator.hasNext()) {
                Map.Entry<String,JsonNode> jsonField = jsonIterator.next();
                String k = jsonField.getKey();
                String v = jsonField.getValue().toString();
                ...

            }
于 2020-09-29T14:21:54.893 回答