Json 在POST/PUT期间,将所有对象作为其ID或嵌入整个对象的单个自定义反序列化器
我想能够发送一个带有json的帖子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}] } 我希望它仍
{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
- 请参阅我的完整答案(),了解如何通过
扩展StdDeserializer并使用默认反序列化ObjectMapper
@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;
}
}