当前位置: 首页 > 知识库问答 >
问题:

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

娄浩荡
2023-03-14

考虑到以下几点。。

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的转换器接口,但这不适用于展开嵌套列表对象。

  • 参见:https://stackoverflow.com/a/41651324/356895

我也知道@JsonUnwrapped,但它不是为列表设计的。

  • jackson json(Java)中的平面结构中序列化List内容
  • Jackson:如何在不修改POJO的情况下向JSON添加自定义属性
  • 如何序列化只有一个孩子的ID与杰克逊
  • 杰克逊列表反序列化。嵌套列表
  • Jackson JSON拦截模块

共有2个答案

鲁熙云
2023-03-14

基于这个链接,我怀疑字段级注释只代表写入值,而不是整个属性。

一个(相当笨拙的)解决方法可能是为整个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;
    }

}
艾照
2023-03-14

您可以使用BeanSerializerModifier直接修改属性名称和值的写入方式。使用它,您可以检测是否存在自定义注释,在本例中,我创建了一个名为@flattcollection的注释。当存在注释时,数组或集合不是使用常规方法编写的,而是由自定义属性编写器(FlatCollectionPropertyWriter)编写的。

这个注释可能会在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;
        }
    }
}
 类似资料:
  • 问题内容: 如何将作为地图的属性序列化为地图的值列表?我已经能够使用吸气剂上的注释进行其他简单的转换。但是,我不确定我想做什么。 问题答案: 我们需要类似的东西,在我们的案例中,我们使用了您所评论的自定义项,这很简单: 使用它的代码:

  • 我有这样一个实体: 和相关的JSON: 我试图在Spring MVC@Controller中以如下方式对其进行反序列化: 添加

  • 我目前正在开发一个Java web应用程序,它使用Magento REST API公开的JSON数据。api返回的数据示例如下: 我的应用程序中有一个Java类,如下所示: 我想对数据进行反序列化,并将其转换为,但我总是得到以下错误: 这是我用来将JSON响应反序列化为ArrayList的语句行: 有人能分享一些见解吗?我看到一些例子,返回的JSON对象前面没有任何ID。那是因为我做错了什么吗?非

  • 问题内容: 我需要将一些对象序列化为JSON并发送到WebService。如何使用org.json库?否则我将不得不使用另一个?这是我需要序列化的类: 我只放了类的变量和构造函数,但也有getter和setter方法。所以如果有人可以帮忙 问题答案: 没有注释的简单方法是使用Gson库 就那么简单:

  • 我正在编写一个Java spring boot mvc应用程序,它可以导出/导入数据。我写了一个包装类,它应该为学生类序列化/反序列化数据。它适用于导出,但在导入过程中出现错误 下面是我的maven jackson依赖项: 最后是转换的方法: 上面提到了Mapper.ReadValue(stream,typeReference)处的MismatchInputException ObjectMapp

  • 我需要反序列化以下json: 将它的< code>id属性设置为< code>foo_id json属性。 我需要在自定义反序列化程序中执行此操作。实现这一点最简单的方法是什么? 我想以某种方式将json“转换”为 然后将此委托给杰克逊。 在本例中,对象的类型为Foo,但其他对象可能不属于此类。另外,在本例中,json是一个数字,但如果它也是一个字符串,我希望支持。所以,我需要一种通用的方法来做到