问题描述
我使用 Jackson
将 POJO
序列化为 CSV
。现在我们需要将某些字段的命名更改为 snake_case。这很容易通过 @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
完成。
例如:
public class Pojo {
private int someField;
}
默认将序列化为“someField”,SnakeCaseStrategy
将序列化为“some_field”。
如何同时获得序列化?:
{
"someField" : "one","some_field" : "one"
}
我的第一次尝试是混合:
public abstract class PojoFormat {
@JsonProperty("someField")
abstract String getSomeField();
}
但这实际上只会撤消命名策略更改。 那么如何在序列化中复制字段 - 最好不要通过更改 Pojo(当所有客户端都可以处理时,应删除此复制的字段)。
小更新:
在我的真实课堂中有一些使用 JsonUnwrapped 的嵌套类,并且文档指出这不适用于自定义序列化程序(不知道这在这里有什么不同)。
解决方法
嗯,我以前从未见过这个,如果本网站有人知道如何,我会很高兴。
在我看来,最简单的方法是使用自定义序列化程序。
例如:
- 使用@JsonSerialize 注释
- 注册一个模块
- 带反射的动态序列化器
@JsonSerialize 注解
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
@JsonSerializer(using=PojoSerializer.class)
class Pojo {
private String myValue;
// getters and setters
}
class PojoSerializer extends StdSerializer<Pojo> {
public PojoSerializer() {
super(Pojo.class);
}
@Override
public void serialize(Pojo value,JsonGenerator gen,SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeStringField("myValue",value.getMyValue());
gen.writeStringField("my_value",value.getMyValue());
gen.writeEndObject();
}
}
模块
static class Pojo {
private String myValue;
public String getMyValue() {
return myValue;
}
public Pojo setMyValue(String myValue) {
this.myValue = myValue;
return this;
}
}
static class PojoSerializer extends StdSerializer<Pojo> {
public PojoSerializer() {
super(Pojo.class);
}
@Override
public void serialize(Pojo value,value.getMyValue());
gen.writeEndObject();
}
}
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule("PojoModule");
module.addSerializer(Pojo.class,new PojoSerializer());
mapper.registerModule(module);
final Pojo pojo = new Pojo();
pojo.setMyValue("This is the value of my pojo");
System.out.println(mapper.writeValueAsString(pojo));
}
反思
我为你写了一些代码,你可能想看看以获得新的想法。 这是一种通用方式(只是为了不编写多个序列化程序)。
// The serializer will be register in the ObjectMapper module.
static class Pojo {
private String myValue = "With snake and camel";
private String value = "Without snake case";
private String thirdValue = "snake & camel";
}
// using the annotation
@JsonSerialize(using = PojoSerializer.class)
static class Pojo2 {
private String pojoName = "Pojo 2";
private String pojo = "pojp";
}
static class PojoSerializer extends StdSerializer<Object> {
public PojoSerializer() {
super(Object.class);
}
@Override
public void serialize(Object value,SerializerProvider provider) throws IOException {
gen.writeStartObject();
final Field[] fields = value.getClass().getDeclaredFields();
for(final Field field : fields) {
final String name = field.getName();
final String fieldValue;
try {
// Do not use this!
fieldValue = (String)field.get(value);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
byte firstUpperCase = -1;
for(byte index = 0; index < name.length(); index++) {
final char caractere = name.charAt(index);
// A ascii code is 66 decimal,and 90 is the Z in decimal
if(caractere > 'A' && caractere < 'Z') {
// found the first upper
firstUpperCase = index;
break;
}
}
// writes the normal field name
gen.writeStringField(name,fieldValue);
// if the name is in camel case,we will write in snake case too.
if(firstUpperCase != -1) {
final char lowerLetter = (char)((int) name.charAt(firstUpperCase) + 32);
final String left = name.substring(0,firstUpperCase);
final String right = String.format("%c%s",lowerLetter,name.substring(firstUpperCase + 1));
gen.writeStringField(String.format("%s_%s",left,right),fieldValue);
}
}
gen.writeEndObject();
}
}
,
您可以尝试使用 JsonAnyGetter 注释并为每个 POJO
额外映射定义以实现向后兼容性。
让我们创建一个简单的界面:
interface CompatibleToVer1 {
@JsonAnyGetter
Map<String,Object> getCompatibilityView();
}
和两个实现它的类:
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
class RootPojo implements CompatibleToVer1 {
private int rootId;
@JsonUnwrapped
private SomePojo pojo;
@Override
public Map<String,Object> getCompatibilityView() {
return Collections.singletonMap("rootId",rootId);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
class SomePojo implements CompatibleToVer1 {
private int someField;
private String someName;
@Override
public Map<String,Object> getCompatibilityView() {
Map<String,Object> extra = new LinkedHashMap<>();
extra.put("someField",someField);
return extra;
}
}
如您所见,我使用自定义名称为每个 POJO
定义了额外的列。序列化到 JSON
很简单:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
SomePojo pojo = new SomePojo(123,"Tom");
mapper.writeValue(System.out,new RootPojo(1,pojo));
以上代码打印:
{
"root_id" : 1,"some_field" : 123,"some_name" : "Tom","someField" : 123,"rootId" : 1
}
但是对于 CSV
,我们需要创建额外的配置:
CsvMapper csvMapper = CsvMapper.builder().build();
CsvSchema pojoExtraScheme = CsvSchema.builder()
.addColumn("someField")
.build();
CsvSchema rootExtraScheme = CsvSchema.builder()
.addColumn("rootId")
.build();
CsvSchema compatibleSchema = CsvSchema.emptySchema()
.withHeader()
.withColumnsFrom(csvMapper.schemaFor(RootPojo.class))
.withColumnsFrom(rootExtraScheme)
.withColumnsFrom(csvMapper.schemaFor(SomePojo.class))
.withColumnsFrom(pojoExtraScheme);
SomePojo tom = new SomePojo(123,"Tom");
SomePojo jerry = new SomePojo(124,"Jerry");
List<RootPojo> pojos = Arrays.asList(new RootPojo(1,tom),new RootPojo(2,jerry));
ObjectWriter writer = csvMapper.writer(compatibleSchema);
System.out.println(writer.writeValueAsString(pojos));
以上代码打印:
some_field,some_name,root_id,rootId,someField
123,Tom,1,123
124,Jerry,2,124
如果你不想指定额外的列两次,你可以基于我们的接口实现 builder 方法:
CsvSchema createSchemaFor(CompatibleToVer1 entity) {
CsvSchema.Builder builder = CsvSchema.builder();
entity.getCompatibilityView().keySet().forEach(builder::addColumn);
return builder.build();
}
并使用如下:
CsvSchema compatibleSchema = CsvSchema.emptySchema()
.withHeader()
.withColumnsFrom(csvMapper.schemaFor(RootPojo.class))
.withColumnsFrom(createSchemaFor(new RootPojo()))
.withColumnsFrom(csvMapper.schemaFor(SomePojo.class))
.withColumnsFrom(createSchemaFor(new SomePojo()));
将 JsonAnyGetter
与 CSV
一起使用非常棘手,并且将其与其他注释混合使用可能会出现问题,请查看:Could please add JsonAnyGetter and JsonAnySetter annotations support?