有没有办法使用 Jackson and/or 其关联库之一(csv、json 等)将字符串转换为 Java 类型?
Is there a way to convert a String to a Java type using Jackson and/or one of its associated libraries (csv, json, etc.)
是否有一种机制可以应用一组标准检查来检测,然后使用 Jackson 的标准文本相关库之一(csv,json将字符串运行sform 为检测到的类型,甚至 jackson-core)?我可以想象将它与与该值关联的标签(例如 CSV header)一起使用来执行如下操作:
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()
需要一组 'extractors' 来应用 t运行sform,并且 class 的消费者必须知道 'ambiguity' 'Object' return 类型,但仍能够根据其目的消费和使用信息。
我目前正在考虑的示例涉及构建 SQL DDL 或 DML,例如 CREATE Table 语句,使用从评估 csv 文件中的行派生的列表中的信息。
经过更多的挖掘,希望能在那里找到一些东西,我写下了我心中所想的开头。
请记住,我在这里的目的不是要展示一些东西 'complete',因为我确定这里缺少一些东西,边缘情况没有解决等等。
pasrse(List<Map<String, String>> rows, List<String> headers
的想法是,例如,这可能是从 Jackson 读取的 CSV 文件中的行样本。
同样,这还不完整,所以我不想挑剔以下所有错误。问题不是 'how would we write this?',而是 'is anyone familiar with something that exists that does something like the following?'.
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
来自一个上下文,其中我有一个来自 CSVReader 的 List<Map<String,String>>
。
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 class,供参考。
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)
});
}
我不知道有任何库可以做到这一点,也从未见过任何以这种方式在一组开放的可能类型上工作的东西。
对于封闭类型集(你知道所有可能的输出类型),更简单的方法是将 class FQN 写在字符串中(如果你在控制写入的字符串)。
完整的 FQN,or an alias to it.
不然我觉得没法不写支票
此外,它会非常微妙,因为我正在考虑边缘用例。
假设您在字符串中使用 json 作为序列化格式,您将如何区分像 Hello World
这样的 String
值和以某种 ISO 格式编写的 Date
(例如 2020-09-22
)。为此,您需要在所做的检查中引入一些优先级(首先尝试使用一些正则表达式检查它是否是日期,如果不是,则使用下一个,简单的字符串是最后一个)
如果你有两个对象怎么办:
String name;
String surname;
}
class Employee {
String name;
String surname;
Integer salary
}
并且您收到了第二种类型的序列化值,但薪水为空(空或 属性 完全缺失)。
如何区分集合和列表?
我不知道您的意图是否如此动态,或者您已经知道所有可能的反序列化类型,也许问题中的更多细节可以提供帮助。
更新
刚看到代码,现在好像更清楚了。
如果你知道所有可能的输出,就是这样。
我要做的唯一更改是简化要管理的类型的增加,从而抽象提取过程。
为此,我认为应该做一些小改动,例如:
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 解析可能会导致讨厌的问题。
你实际上想做的是写一个parser。您将片段翻译成解析树。解析树捕获类型和值。对于数组和对象等分层类型,每个树节点都包含子节点。
最常用的解析器之一(尽管对您的用例来说有点矫枉过正)是 Antlr. Antlr brings out-of-the-box support for Json.
我建议花时间消化所有涉及的概念。尽管一开始看起来有点矫枉过正,但当您进行任何类型的扩展时,它很快就会得到回报。更改语法相对容易;生成的代码非常复杂。此外,所有解析器生成器都会验证您的语法以显示逻辑错误。
当然,如果您仅限于解析 CSV 或 JSON(而不是同时解析两者),您应该使用现有库的解析器。比如jackson有ObjectMapper.readTree来获取解析树。您还可以使用 ObjectMapper.readValue(<fragment>, Object.class)
来简单地获取规范的 java 类.
试试这个:
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();
...
}
是否有一种机制可以应用一组标准检查来检测,然后使用 Jackson 的标准文本相关库之一(csv,json将字符串运行sform 为检测到的类型,甚至 jackson-core)?我可以想象将它与与该值关联的标签(例如 CSV header)一起使用来执行如下操作:
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()
需要一组 'extractors' 来应用 t运行sform,并且 class 的消费者必须知道 'ambiguity' 'Object' return 类型,但仍能够根据其目的消费和使用信息。
我目前正在考虑的示例涉及构建 SQL DDL 或 DML,例如 CREATE Table 语句,使用从评估 csv 文件中的行派生的列表中的信息。
经过更多的挖掘,希望能在那里找到一些东西,我写下了我心中所想的开头。
请记住,我在这里的目的不是要展示一些东西 'complete',因为我确定这里缺少一些东西,边缘情况没有解决等等。
pasrse(List<Map<String, String>> rows, List<String> headers
的想法是,例如,这可能是从 Jackson 读取的 CSV 文件中的行样本。
同样,这还不完整,所以我不想挑剔以下所有错误。问题不是 'how would we write this?',而是 'is anyone familiar with something that exists that does something like the following?'.
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
来自一个上下文,其中我有一个来自 CSVReader 的 List<Map<String,String>>
。
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 class,供参考。
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)
});
}
我不知道有任何库可以做到这一点,也从未见过任何以这种方式在一组开放的可能类型上工作的东西。
对于封闭类型集(你知道所有可能的输出类型),更简单的方法是将 class FQN 写在字符串中(如果你在控制写入的字符串)。
完整的 FQN,or an alias to it.
不然我觉得没法不写支票
此外,它会非常微妙,因为我正在考虑边缘用例。
假设您在字符串中使用 json 作为序列化格式,您将如何区分像 Hello World
这样的 String
值和以某种 ISO 格式编写的 Date
(例如 2020-09-22
)。为此,您需要在所做的检查中引入一些优先级(首先尝试使用一些正则表达式检查它是否是日期,如果不是,则使用下一个,简单的字符串是最后一个)
如果你有两个对象怎么办:
String name;
String surname;
}
class Employee {
String name;
String surname;
Integer salary
}
并且您收到了第二种类型的序列化值,但薪水为空(空或 属性 完全缺失)。
如何区分集合和列表?
我不知道您的意图是否如此动态,或者您已经知道所有可能的反序列化类型,也许问题中的更多细节可以提供帮助。
更新
刚看到代码,现在好像更清楚了。
如果你知道所有可能的输出,就是这样。
我要做的唯一更改是简化要管理的类型的增加,从而抽象提取过程。
为此,我认为应该做一些小改动,例如:
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 解析可能会导致讨厌的问题。
你实际上想做的是写一个parser。您将片段翻译成解析树。解析树捕获类型和值。对于数组和对象等分层类型,每个树节点都包含子节点。
最常用的解析器之一(尽管对您的用例来说有点矫枉过正)是 Antlr. Antlr brings out-of-the-box support for Json.
我建议花时间消化所有涉及的概念。尽管一开始看起来有点矫枉过正,但当您进行任何类型的扩展时,它很快就会得到回报。更改语法相对容易;生成的代码非常复杂。此外,所有解析器生成器都会验证您的语法以显示逻辑错误。
当然,如果您仅限于解析 CSV 或 JSON(而不是同时解析两者),您应该使用现有库的解析器。比如jackson有ObjectMapper.readTree来获取解析树。您还可以使用 ObjectMapper.readValue(<fragment>, Object.class)
来简单地获取规范的 java 类.
试试这个:
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();
...
}