Java 如何使用Jackson将列表内容序列化为平面JSON对象?

Java 如何使用Jackson将列表内容序列化为平面JSON对象?,java,json,serialization,jackson,nested,Java,Json,Serialization,Jackson,Nested,鉴于以下情况: public class City { private String title; private List<Person> people; } 我希望将这些类的实例序列化为以下示例JSON: JSON格式是由外部API定义的,我无法更改 我已经发现我可以使用自定义序列化程序对列表字段进行注释,例如: @JsonSerialize(using = PeopleSerializer.class) private List<Person>

鉴于以下情况:

public class City {

    private String title;
    private List<Person> people;
}
我希望将这些类的实例序列化为以下示例JSON:

JSON格式是由外部API定义的,我无法更改

我已经发现我可以使用自定义序列化程序对列表字段进行注释,例如:

@JsonSerialize(using = PeopleSerializer.class)
private List<Person> people;
我还研究了使用Jackson的界面,但这不适合展开嵌套的列表对象

  • 见:
我也知道
@JsonUnwrapped
,但它不是设计用于列表的

相关职位
相关职位(反序列化)
相关图书馆
基于此,我怀疑字段级注释仅代表写入值,而不是整个属性

一个(相当麻烦的)解决方法可能是为整个City类提供一个自定义序列化程序:

@JsonSerialize(using = CitySerializer.class)
public class City {
    private String title;
    @JsonIgnore
    private List<Person> people;
}
@JsonSerialize(使用=CitySerializer.class)
公营城市{
私有字符串标题;
@杰索尼奥雷
私人名单;
}
…然后

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;
    }

}
公共类CitySerializer扩展了JsonSerializer{
私有静态最终int START_索引=1;
@凌驾
公共空间连载(城市,
JSONG发电机,
SerializerProvider提供程序)引发IOException{
generator.writeStartObject();
//写入所有属性(忽略的除外)
JavaType JavaType=provider.constructType(City.class);
BeanDescription beanDesc=provider.getConfig().introspect(javaType);
JsonSerializer serializer=BeanSerializerFactory.instance.findBeanSerializer(提供程序,
javaType,
beanDesc);
序列化(值,jgen,provider)`
//自定义人员序列化
List people=city.getPeople();
for(int i=0;i
您可以使用
BeanSerializerModifier
直接修改属性名称和值的写入方式。使用它,您可以检测是否存在自定义注释,在本例中,我创建了一个名为
@flattcollection
的注释。当存在注释时,数组或集合不是使用普通方法编写的,而是由自定义属性编写器(
FlatCollectionPropertyWriter
)编写的

这个注释可能会在2d数组或其他边缘情况下中断,我还没有测试过它们,但您可以为它们编写代码而不会遇到太多麻烦,至少会抛出一个有意义的错误

这是完整的工作代码。值得注意的是

  • FlatterCollectionSerializerModifier.changeProperties
  • FlatCollectionPropertyWriter.serializeAsField
  • 我为你准备的两个托多
输出:

{
  "titleCity" : "New York",
  "personName_1" : "Foo",
  "personAge_1" : 123,
  "personName_2" : "Baz",
  "personAge_2" : 22
}
代码:

import com.fasterxml.jackson.core.JsonGenerator;
导入com.fasterxml.jackson.databind.*;
导入com.fasterxml.jackson.databind.ser.*;
导入com.fasterxml.jackson.databind.util.NameTransformer;
导入java.lang.annotation.ElementType;
导入java.lang.annotation.Retention;
导入java.lang.annotation.RetentionPolicy;
导入java.lang.annotation.Target;
导入java.util.*;
公共类SO45698499{
公共静态void main(字符串[]args)引发异常{
ObjectWriter writer=createMapper().writerWithDefaultPrettyPrinter();
String val=writer.writeValueAsString(纽约),
数组.asList(新人(“Foo”,123),新人(“Baz”,22));
系统输出打印项次(val);
}
/**
*构造映射器时要考虑序列化器修饰符
*@返回
*/
公共静态对象映射器createMapper(){
FlattCollectionSerializerModifier修饰符=新的FlattCollectionSerializerModifier();
SerializerFactory sf=BeanSerializerFactory.instance.withSerializerModifier(修饰符);
ObjectMapper mapper=新的ObjectMapper();
mapper.setSerializerFactory(sf);
返回映射器;
}
@目标({ElementType.ANNOTATION_TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER})
@保留(RetentionPolicy.RUNTIME)
公共@interface集合{
}
/**
*查找集合注释并修改bean编写器
*/
公共静态类FlattCollectionSerializerModifier扩展BeanSerializerModifier{
@凌驾
公共列表更改属性(SerializationConfig配置、BeanDescription beanDesc、列表beanProperties){
对于(int i=0;iJsonGenerationException: Can not write a field name, expecting a value
@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;
    }

}
{
  "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;
        }
    }
}