Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/json/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Json 在POST/PUT期间,将所有对象作为其ID或嵌入整个对象的单个自定义反序列化器_Json_Spring_Jackson_Spring Data_Spring Data Jpa - Fatal编程技术网

Json 在POST/PUT期间,将所有对象作为其ID或嵌入整个对象的单个自定义反序列化器

Json 在POST/PUT期间,将所有对象作为其ID或嵌入整个对象的单个自定义反序列化器,json,spring,jackson,spring-data,spring-data-jpa,Json,Spring,Jackson,Spring Data,Spring Data Jpa,我想能够发送一个带有json的帖子 {name:“name”,类别:2,次要类别:[3,4,5]}来自客户端 并且能够像以下那样进行反序列化: @Entity public Category { @Id public int id; public String name; } 以防它被当作 { name: "name", category: {id: 2 }, secondaryCategories: [{id: 3}, {id: 4}, {id: 5}] } 我希望它仍

我想能够发送一个带有json的帖子

{name:“name”,类别:2,次要类别:[3,4,5]}
来自客户端

并且能够像以下那样进行反序列化:

@Entity
public Category {
   @Id
   public int id;

   public String name;
}
以防它被当作

{ name: "name", category: {id: 2 }, secondaryCategories: [{id: 3}, {id: 4}, {id: 5}] }
我希望它仍然像现在一样工作

我需要什么样的注释和自定义反序列化器?希望反序列化程序可以用于所有可能的对象,这些对象的id是一个属性

谢谢

编辑

  • 请参阅首选zafrost的
    @JsonCreator
    答案()
  • 请参阅我的完整答案(),了解如何通过
    ObjectMapper
    扩展StdDeserializer并使用默认反序列化

您可以尝试几个选项,实际上自定义反序列化程序/序列化程序可能有意义,但您也可以通过
@JsonIdentityInfo
(反序列化)+
@JsonIdentityReference
(如果需要序列化为整数)注释来实现这一点


反序列化 因此,您需要使用
@JsonIdentityInfo

Work both for 
{ "category":1 }
{ "category":{ "id":1 }
这里最困难的部分是,您实际上必须编写自定义的
objectdresolver
,以解析来自db/其他源的对象。在下面示例中的
myObjectdResolver.resolveId
方法中查看我的简单反射版本:

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "id", 
        scope = Product.class,  // different for each class
        resolver = MyObjectIdResolver.class)
默认行为如下所述: 并将为第一个元素生成对象,然后为每个元素生成id。Jackson中的ID/引用机制可以使对象实例只被完全序列化一次,并在其他地方被其ID引用

选项1。(始终作为id)

需要在每个对象字段上方使用
@jsonidentialreference(alwaysAsId=true)
(可以在页面底部的演示中取消注释)

选项2。(始终作为完整对象表示)

此选项很棘手,因为您必须以某种方式删除所有IdentityInfo以进行序列化。一个选项是使用2个对象映射器。1用于序列化,2-nd用于反序列化,并配置某种mixin或
@JsonView

另一种更容易实现的方法是使用
SerializationConfig
完全忽略
@JsonIdentityInfo
注释

Works for 
{ "category" : { "id":1 , "name":null} , secondaryCategories: [{ "id":1 , "name":null} , { "id":2 , "name":null}]}
可能更好的方法是以相同的方式为反序列化配置定义
@JsonIdentityInfo
,并删除类上面的所有注释。差不多

此时,您可能希望只编写自定义序列化程序/反序列化程序


下面是正在工作的(没有spring的简单Jackson)演示:

import com.fasterxml.jackson.annotation.*;
导入com.fasterxml.jackson.databind.ObjectMapper;
导入java.lang.reflect.Constructor;
导入java.lang.reflect.InvocationTargetException;
导入java.lang.reflect.Method;
导入java.util.Set;
公共班机{
@JsonIdentityInfo(生成器=ObjectedGenerators.PropertyGenerator.class,
property=“id”,
解析器=myObjectdResolver.class,
范围=类别(类别)
公共静态类类别{
@JsonProperty(“id”)
公共int id;
@JsonProperty(“名称”)
公共字符串名称;
公共int getId(){
返回id;
}
公共无效集合id(内部id){
this.id=id;
}
@凌驾
公共字符串toString(){
返回“类别{”+
“id=”+id+
“,name=”“+name+”\“””+
'}';
}
}
@JsonIdentityInfo(生成器=ObjectedGenerators.PropertyGenerator.class,
property=“id”,
解析器=myObjectdResolver.class,
范围=Product.class)
公共静态类产品{
@JsonProperty(“id”)
公共int id;
@JsonProperty(“名称”)
公共字符串名称;
//仅当需要序列化时才需要@JsonIdentityReference
//@jsonidentialreference(alwaysAsId=true)
@JsonProperty(“类别”)
类别;
//仅当需要序列化时才需要@JsonIdentityReference
//@jsonidentialreference(alwaysAsId=true)
@JsonProperty(“第二类”)
设置二级分类;
公共int getId(){
返回id;
}
公共无效集合id(内部id){
this.id=id;
}
@凌驾
公共字符串toString(){
返回“产品{”+
“id=”+id+
“,name=”“+name+”\“””+
“,category=“+category+
“,secondaryCategories=“+secondaryCategories+
'}';
}
}
私有静态类MyObjectResolver实现ObjectResolver{
私人地图项目;
@凌驾
public void bindItem(ObjectedGenerator.IdKey id,对象pojo){
如果(_items==null){
_items=newhashmap();
}如果(!\u items.containsKey(id))
_项目。放置(id,pojo);
}
@凌驾
公共对象解析id(ObjectedGenerator.IdKey id){
Object Object=(\u items==null)?null:\u items.get(id);
if(object==null){
试一试{
//创建实例
Constructor=id.scope.getConstructor();
object=ctor.newInstance();
//设置id
方法setId=id.scope.getDeclaredMethod(“setId”,int.class);
调用(对象,id.key);
// https://github.com/FasterXML/jackson-databind/issues/372
//bindItem(id,对象);导致奇怪的行为
}渔获物(NoSuchMethodException | IllegalA)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "id", 
        scope = Product.class,  // different for each class
        resolver = MyObjectIdResolver.class)
private static class MyObjectIdResolver implements ObjectIdResolver {
    private Map<ObjectIdGenerator.IdKey,Object> _items  = new HashMap<>();

    @Override
    public void bindItem(ObjectIdGenerator.IdKey id, Object pojo) {
        if (!_items.containsKey(id)) _items.put(id, pojo);
    }

    @Override
    public Object resolveId(ObjectIdGenerator.IdKey id) {
        Object object = _items.get(id);
        return object == null ? getById(id) : object;
    }

    protected Object getById(ObjectIdGenerator.IdKey id){
        Object object = null;
        try {
            // todo objectRepository.getById(idKey.key, idKey.scope)
            object = id.scope.getConstructor().newInstance(); // create instance
            id.scope.getField("id").set(object, id.key);  // set id
            bindItem(id, object);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return object;
    }

    @Override
    public ObjectIdResolver newForDeserialization(Object context) {
        return this;
    }

    @Override
    public boolean canUseFor(ObjectIdResolver resolverType) {
        return resolverType.getClass() == getClass();
    }
}
Default behavior
{ "category":{ "id":1 , "name":null} , secondaryCategories: [1 , { { "id":2 , "name":null} ]}
Works for 
{ "category":1 , secondaryCategories:[1 , 2]}
Works for 
{ "category" : { "id":1 , "name":null} , secondaryCategories: [{ "id":1 , "name":null} , { "id":2 , "name":null}]}
@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();

    SerializationConfig config = mapper.getSerializationConfig()
            .with(new JacksonAnnotationIntrospector() {
        @Override
        public ObjectIdInfo findObjectIdInfo(final Annotated ann) {
            return null;
        }
    });

    mapper.setConfig(config);

    return mapper;
}
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;

public class Main {

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            resolver = MyObjectIdResolver.class,
            scope = Category.class)
    public static class Category {
        @JsonProperty("id")
        public int id;
        @JsonProperty("name")
        public String name;

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "Category{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            resolver = MyObjectIdResolver.class,
            scope = Product.class)
    public static class Product {
        @JsonProperty("id")
        public int id;
        @JsonProperty("name")
        public String name;

        // Need @JsonIdentityReference only if you want the serialization
        // @JsonIdentityReference(alwaysAsId = true)
        @JsonProperty("category")
        Category category;

        // Need @JsonIdentityReference only if you want the serialization
        // @JsonIdentityReference(alwaysAsId = true)
        @JsonProperty("secondaryCategories")
        Set<Category> secondaryCategories;

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "Product{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", category=" + category +
                    ", secondaryCategories=" + secondaryCategories +
                    '}';
        }
    }

    private static class MyObjectIdResolver implements ObjectIdResolver {

       private Map<ObjectIdGenerator.IdKey,Object> _items;

        @Override
        public void bindItem(ObjectIdGenerator.IdKey id, Object pojo) {
            if (_items == null) {
                _items = new HashMap<ObjectIdGenerator.IdKey,Object>();
            } if (!_items.containsKey(id))
                _items.put(id, pojo);
        }

        @Override
        public Object resolveId(ObjectIdGenerator.IdKey id) {
            Object object = (_items == null) ? null : _items.get(id);
            if (object == null) {
                try {

                    // create instance
                    Constructor<?> ctor = id.scope.getConstructor();
                    object = ctor.newInstance();

                    // set id
                    Method setId = id.scope.getDeclaredMethod("setId", int.class);
                    setId.invoke(object, id.key);
                    // https://github.com/FasterXML/jackson-databind/issues/372
                    // bindItem(id, object); results in strange behavior

                } catch (NoSuchMethodException | IllegalAccessException
                        | InstantiationException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
            return object;
        }

        @Override
        public ObjectIdResolver newForDeserialization(Object context) {
            return new MyObjectIdResolver();
        }

        @Override
        public boolean canUseFor(ObjectIdResolver resolverType) {
            return resolverType.getClass() == getClass();
        }
    }

    public static void main(String[] args) throws Exception {
        String str = "{ \"name\": \"name\", \"category\": {\"id\": 2 }, " +
                "\"secondaryCategories\":[{\"id\":3},{\"id\":4},{\"id\":5}]}";

        // from  str
        Product product = new ObjectMapper().readValue(str, Product.class);
        System.out.println(product);

        // to json
        String productStr = new ObjectMapper().writeValueAsString(product);
        System.out.println(productStr);

        String str2 = "{ \"name\": \"name\", \"category\":  2, " +
                "\"secondaryCategories\": [ 3,  4,  5] }";

        // from  str2
        Product product2 = new ObjectMapper().readValue(str2, Product.class);
        System.out.println(product2);

        // to json
        String productStr2 = new ObjectMapper().writeValueAsString(product2);
        System.out.println(productStr2);
    }
}
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.springframework.context.annotation.Bean;

import java.io.IOException;

public class IdWrapperDeserializer<T> extends StdDeserializer<T> {

    private Class<T> clazz;

    public IdWrapperDeserializer(Class<T> clazz) {
        super(clazz);
        this.clazz = clazz;
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        return mapper;
    }

    @Override
    public T deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
        String json = jp.readValueAsTree().toString();

        T obj = null;
        int id = 0;
        try {
            id = Integer.parseInt(json);
        }
        catch( Exception e) {
            obj = objectMapper().readValue(json, clazz);
            return obj;
        }
        try {
            obj = clazz.newInstance();
            ReflectionUtils.set(obj,"id",id);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return obj;
    }

}
@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
    mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

    mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
    mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);

    SimpleModule testModule = new SimpleModule("MyModule")
            .addDeserializer(Category.class, new IdWrapperDeserializer(Category.class))

    mapper.registerModule(testModule);

    return mapper;
}
public class ReflectionUtils {
    // 
    public static boolean set(Object object, String fieldName, Object fieldValue) {
        Class<?> clazz = object.getClass();
        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                field.set(object, fieldValue);
                return true;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }
        return false;
    }
}
private class Product {
    @JsonProperty("category")
    private Category category;

    @JsonProperty("secondaryCategories")
    private List<Category> secondaryCategories;
}


private class Category {
    @JsonProperty("id")
    private int id;

    @JsonCreator
    public static Category factory(int id){
        Category p = new Category();
        p.id = id;
        // or some db call 
        return p;
    }
}
private class Category {
    private int id;

    public Category() {}

    @JsonCreator
    public Category(int id) {
        this.id = id;
    }
}