鉴于以下POJO ..
public class City {
private String title;
private List<Person> people;
}
...
public class Person {
private String name;
private int age;
}
我想让Jackson将类的实例序列化为以下示例 JSON:
{
"title" : "New York",
"personName_1" : "Jane Doe",
"personAge_1" : 42,
"personName_2" : "John Doe",
"personAge_2" : 23
}
JSON格式由外部API定义,我无法更改。
我已经发现我可以使用自定义序列化程序注释列表字段,例如:
@JsonSerialize(using = PeopleSerializer.class)
private List<Person> people;
...这是我尝试过的基本实现:
public class PeopleSerializer extends JsonSerializer<List<Person>> {
private static final int START_INDEX = 1;
@Override
public void serialize(List<Person> people,
JsonGenerator generator,
SerializerProvider provider) throws IOException {
for (int i = 0; i < people.size(); ++i) {
Person person = people.get(i);
int index = i + START_INDEX;
serialize(person, index, generator);
}
}
private void serialize(Person person, int index, JsonGenerator generator) throws IOException {
generator.writeStringField(getIndexedFieldName("personName", index),
person.getName());
generator.writeNumberField(getIndexedFieldName("personAge", index),
person.getAge());
}
private String getIndexedFieldName(String fieldName, int index) {
return fieldName + "_" + index;
}
}
然而,这失败了:
JsonGenerationException: Can not write a field name, expecting a value
我还研究过使用Jackson的Converter界面,但这不适合展开嵌套列表对象。
我也知道@JsonUnwrapped
,但它不适用于列表。
答案 0 :(得分:4)
您可以使用BeanSerializerModifier
直接修改属性名称和值的写入方式。使用此方法,您可以检测是否存在自定义注释,在这种情况下,我创建了一个名为@FlattenCollection
的注释。当注释出现时,数组或集合不是使用常规方法编写的,而是由自定义属性编写器(FlattenCollectionPropertyWriter
)编写的。
这个注释可能会破坏2d数组或其他边缘情况,我还没有对它们进行过测试,但你可以编写它们而不会有太多麻烦,至少会抛出一个有意义的错误。
这是完整的工作代码。值得关注的是
输出:
{
"titleCity" : "New York",
"personName_1" : "Foo",
"personAge_1" : 123,
"personName_2" : "Baz",
"personAge_2" : 22
}
代码:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.*;
import com.fasterxml.jackson.databind.util.NameTransformer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.*;
public class SO45698499 {
public static void main(String [] args) throws Exception {
ObjectWriter writer = createMapper().writerWithDefaultPrettyPrinter();
String val = writer.writeValueAsString(new City("New York",
Arrays.asList(new Person("Foo", 123), new Person("Baz", 22))));
System.out.println(val);
}
/**
* Constructs our mapper with the serializer modifier in mind
* @return
*/
public static ObjectMapper createMapper() {
FlattenCollectionSerializerModifier modifier = new FlattenCollectionSerializerModifier();
SerializerFactory sf = BeanSerializerFactory.instance.withSerializerModifier(modifier);
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializerFactory(sf);
return mapper;
}
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FlattenCollection {
}
/**
* Looks for the FlattenCollection annotation and modifies the bean writer
*/
public static class FlattenCollectionSerializerModifier extends BeanSerializerModifier {
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (int i = 0; i < beanProperties.size(); i++) {
BeanPropertyWriter writer = beanProperties.get(i);
FlattenCollection annotation = writer.getAnnotation(FlattenCollection.class);
if (annotation != null) {
beanProperties.set(i, new FlattenCollectionPropertyWriter(writer));
}
}
return beanProperties;
}
}
/**
* Instead of writing a collection as an array, flatten the objects down into values.
*/
public static class FlattenCollectionPropertyWriter extends BeanPropertyWriter {
private final BeanPropertyWriter writer;
public FlattenCollectionPropertyWriter(BeanPropertyWriter writer) {
super(writer);
this.writer = writer;
}
@Override
public void serializeAsField(Object bean,
JsonGenerator gen,
SerializerProvider prov) throws Exception {
Object arrayValue = writer.get(bean);
// lets try and look for array and collection values
final Iterator iterator;
if(arrayValue != null && arrayValue.getClass().isArray()) {
// deal with array value
iterator = Arrays.stream((Object[])arrayValue).iterator();
} else if(arrayValue != null && Collection.class.isAssignableFrom(arrayValue.getClass())) {
iterator = ((Collection)arrayValue).iterator();
} else {
iterator = null;
}
if(iterator == null) {
// TODO: write null? skip? dunno, you gonna figure this one out
} else {
int index=0;
while(iterator.hasNext()) {
index++;
Object value = iterator.next();
if(value == null) {
// TODO: skip null values and still increment or maybe dont increment? You decide
} else {
// TODO: OP - update your prefix/suffix here, its kinda weird way of making a prefix
final String prefix = value.getClass().getSimpleName().toLowerCase();
final String suffix = "_"+index;
prov.findValueSerializer(value.getClass())
.unwrappingSerializer(new FlattenNameTransformer(prefix, suffix))
.serialize(value, gen, prov);
}
}
}
}
}
public static class FlattenNameTransformer extends NameTransformer {
private final String prefix;
private final String suffix;
public FlattenNameTransformer(String prefix, String suffix) {
this.prefix = prefix;
this.suffix = suffix;
}
@Override
public String transform(String name) {
// captial case the first letter, to prepend the suffix
String transformedName = Character.toUpperCase(name.charAt(0)) + name.substring(1);
return prefix + transformedName + suffix;
}
@Override
public String reverse(String transformed) {
if (transformed.startsWith(prefix)) {
String str = transformed.substring(prefix.length());
if (str.endsWith(suffix)) {
return str.substring(0, str.length() - suffix.length());
}
}
return null;
}
@Override
public String toString() { return "[FlattenNameTransformer('"+prefix+"','"+suffix+"')]"; }
}
/*===============================
* POJOS
===============================*/
public static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public static class City {
private String titleCity;
private List<Person> people;
public City(String title, List<Person> people) {
this.titleCity = title;
this.people = people;
}
public String getTitleCity() {
return titleCity;
}
public void setTitleCity(String titleCity) {
this.titleCity = titleCity;
}
@FlattenCollection
public List<Person> getPeople() {
return people;
}
public void setPeople(List<Person> people) {
this.people = people;
}
}
}
答案 1 :(得分:1)
基于this link我怀疑字段级注释只委托写入值而不是整个属性。
一个(相当kludgey)的解决方法可能是为整个City类设置一个自定义序列化器:
@JsonSerialize(using = CitySerializer.class)
public class City {
private String title;
@JsonIgnore
private List<Person> people;
}
......然后
public class CitySerializer extends JsonSerializer<City> {
private static final int START_INDEX = 1;
@Override
public void serialize(City city,
JsonGenerator generator,
SerializerProvider provider) throws IOException {
generator.writeStartObject();
// Write all properties (except ignored)
JavaType javaType = provider.constructType(City.class);
BeanDescription beanDesc = provider.getConfig().introspect(javaType);
JsonSerializer<Object> serializer = BeanSerializerFactory.instance.findBeanSerializer(provider,
javaType,
beanDesc);
serializer.unwrappingSerializer(null).serialize(value, jgen, provider);`
// Custom serialization of people
List<Person> people = city.getPeople();
for (int i = 0; i < people.size(); ++i) {
Person person = people.get(i);
int index = i + START_INDEX;
serialize(person, index, generator);
}
generator.writeEndObject();
}
private void serialize(Person person, int index, JsonGenerator generator) throws IOException {
generator.writeStringField(getIndexedFieldName("personName", index),
person.getName());
generator.writeNumberField(getIndexedFieldName("personAge", index),
person.getAge());
}
private String getIndexedFieldName(String fieldName, int index) {
return fieldName + "_" + index;
}
}