如何忽略像 "car":{} 这样的空 json 对象,这些对象在使用 jackson 反序列化后会导致空的 pojo
How ignore empty json objects like "car":{}, that cause empty pojos after deserialisation with jackson
我有一个休息 service 消耗 json 来自 Angular UI 和其他休息客户端。基于约 50 个实体的复杂结构的数据存储在约 50 table 的数据库中。问题是可选的 OneToOne 关系,因为 Angular 将可选对象作为空定义发送,如 "car": {},
。 spring 数据存储库将它们保存为空条目,我得到了 Json 类似 "car": {"id": 545234, "version": 0}
的响应。我发现没有 Jackson 注释可以忽略空对象,只有空或 null 属性。
员工实体具有以下形式:
@Entity
public class Employee {
@Id
@GeneratedValue
private Long id;
@Version
private Long version;
private String name;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "car_id")
@JsonManagedReference
private Car car;
.
. Desk, getters and setters
.
}
和OneToOne Reference的另一面
@Entity
public class Car{
@Id
@GeneratedValue
private Long id;
@Version
private Long version;
private String name;
@OneToOne(fetch = FetchType.LAZY, mappedBy = "employee")
@JsonBackReference
private Employee employee;
.
. getters and setters
.
}
例如,我将此作为 post 操作发送到我的服务
{
"name": "ACME",
.
.
.
"employee": {
"name": "worker 1",
"car": {},
"desk": {
floor: 3,
number: 4,
phone: 444
}
.
.
.
},
"addresses": [],
"building": {},
.
.
.
}
我得到了保存的数据作为响应
{
"id": 34534,
"version": 0,
"name": "ACME",
.
.
.
"employee": {
"id": 34535,
"version":0,
"name": "worker 1",
"car": {"id": 34536, "version": 0},
"desk": {
"id": 34538,
"version":0,
"floor": 3,
"number": 4,
"phone": 444
}
.
.
.
},
"addresses": [],
"building": {"id": 34539, "version": 0},
.
.
.
}
正如在响应中看到的那样,我得到了带有 id、版本、许多空值和空字符串的空 table 行,因为当我保存(持久化)主要反序列化公司时 class,另一个实体也被保存,因为它们被注释为级联。
我发现了很多像 这样的例子,有一个具体的 pojo 和一个具体的反序列化器正在工作,但每个实体都需要他自己的反序列化器。这会导致当前实体和未来新实体(仅可选实体)的许多工作。
我尝试了以下方法,我写了一个 BeanDeserializerModifier
并尝试在标准 beandeserializer 上包装一个自己的反序列化器:
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public List<BeanPropertyDefinition> updateProperties(DeserializationConfig config,
BeanDescription beanDesc,
List<BeanPropertyDefinition> propDefs) {
logger.debug("update properties, beandesc: {}", beanDesc.getBeanClass().getSimpleName());
return super.updateProperties(config, beanDesc, propDefs);
}
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
JsonDeserializer<?> deserializer) {
logger.debug("modify deserializer {}",beanDesc.getBeanClass().getSimpleName());
// This fails:
// return new DeserializationWrapper(deserializer, beanDesc);
return deserializer; // This works, but it is the standard behavior
}
});
这是包装器(和错误):
public class DeserializationWrapper extends JsonDeserializer<Object> {
private static final Logger logger = LoggerFactory.getLogger( DeserializationWrapper.class );
private final JsonDeserializer<?> deserializer;
private final BeanDescription beanDesc;
public DeserializationWrapper(JsonDeserializer<?> deserializer, BeanDescription beanDesc) {
this.deserializer = deserializer;
this.beanDesc = beanDesc;
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
logger.debug("deserialize in wrapper {} ",beanDesc.getBeanClass().getSimpleName());
final Object deserialized = deserializer.deserialize(p, ctxt);
ObjectCodec codec = p.getCodec();
JsonNode node = codec.readTree(p);
// some logig that not work
// here. The Idea is to detect with the json parser that the node is empty.
// If it is empty I will return null here and not the deserialized pojo
return deserialized;
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue) throws IOException {
logger.debug("deserializer - method 2");
intoValue = deserializer.deserialize(p, ctxt);
return intoValue;
}
@Override
public boolean isCachable() {
return deserializer.isCachable();
}
.
. I try to wrap the calls to the deserializer
.
Deserialization Wrapper 不工作并在第一次调用后崩溃 com.fasterxml.jackson.databind.exc.MismatchedInputException: No _valueDeserializer assigned at [Source: (PushbackInputStream); line: 2, column: 11] (through reference chain: ... Company["name"])
我的问题:有没有办法扩展工作标准反序列化器的行为,反序列化器在解析时检测到当前 jsonNode 为空且 return null而不是空的 class 实例?也许我的想法是错误的,还有完全不同的解决方案?
在Angular UI 方面解决它是没有选择的。我们使用 Jackson 2.9.5.
将 BeanDeserializerModifier
与自定义解串器一起使用是个好主意。您只需要改进反序列化器的实现。在您的示例中,问题出在这些行中:
final Object deserialized = deserializer.deserialize(p, ctxt); //1.
ObjectCodec codec = p.getCodec(); //2.
JsonNode node = codec.readTree(p); //3.
第 1.
行显示 JSON Object
。在行 2.
和 3.
中,您想创建一个 JsonNode
,但空的 JSON Object
已在行 1.
中读取。接下来的两行将尝试将其余的有效负载读取为 JsonNode
.
Jackson
默认使用 BeanDeserializer
反序列化常规 POJO
classes。我们可以扩展这个 class 并提供我们自己的实现。在版本 2.10.1
deserialise
中,方法如下所示:
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
// common case first
if (p.isExpectedStartObjectToken()) {
if (_vanillaProcessing) {
return vanillaDeserialize(p, ctxt, p.nextToken());
}
// 23-Sep-2015, tatu: This is wrong at some many levels, but for now... it is
// what it is, including "expected behavior".
p.nextToken();
if (_objectIdReader != null) {
return deserializeWithObjectId(p, ctxt);
}
return deserializeFromObject(p, ctxt);
}
return _deserializeOther(p, ctxt, p.getCurrentToken());
}
在大多数情况下,在不需要特殊处理的情况下,vanillaDeserialize
方法将被调用。让我们来看看:
private final Object vanillaDeserialize(JsonParser p, DeserializationContext ctxt, JsonToken t) throws IOException {
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.getCurrentName();
do {
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
}
return bean;
}
如您所见,它几乎完成了我们想要的一切,除了它甚至为空 JSON Object
创建新对象。它会在创建新对象后立即检查字段是否存在。一行太远了。不幸的是,这个方法是私有的,我们不能覆盖它。让我们把它复制到我们的 class 并稍微修改一下:
class EmptyObjectIsNullBeanDeserializer extends BeanDeserializer {
EmptyObjectIsNullBeanDeserializer(BeanDeserializerBase src) {
super(src);
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (_vanillaProcessing) {
return vanillaDeserialize(p, ctxt);
}
return super.deserialize(p, ctxt);
}
private Object vanillaDeserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
p.nextToken();
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
String propName = p.getCurrentName();
do {
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
return bean;
}
return null;
}
}
您可以像下面这样注册:
class EmptyObjectIsNullBeanDeserializerModifier extends BeanDeserializerModifier {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass() == Car.class) { //TODO: change this condition
return new EmptyObjectIsNullBeanDeserializer((BeanDeserializerBase) deserializer);
}
return super.modifyDeserializer(config, beanDesc, deserializer);
}
}
简单 POC
:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.Data;
import java.io.File;
import java.io.IOException;
public class EmptyObjectIsNullApp {
public static void main(String[] args) throws IOException {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
SimpleModule emptyObjectIsNullModule = new SimpleModule();
emptyObjectIsNullModule.setDeserializerModifier(new EmptyObjectIsNullBeanDeserializerModifier());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(emptyObjectIsNullModule);
Wrapper wrapper = mapper.readValue(jsonFile, Wrapper.class);
System.out.println(wrapper);
}
}
@Data
class Wrapper {
private Car car;
}
@Data
class Car {
private int id;
}
对于“空对象”JSON
负载:
{
"car": {}
}
以上代码打印:
Wrapper(car=null)
对于 JSON
具有某些字段的负载:
{
"car": {
"id": 1
}
}
以上代码打印:
Wrapper(car=Car(id=1))
我有一个休息 service 消耗 json 来自 Angular UI 和其他休息客户端。基于约 50 个实体的复杂结构的数据存储在约 50 table 的数据库中。问题是可选的 OneToOne 关系,因为 Angular 将可选对象作为空定义发送,如 "car": {},
。 spring 数据存储库将它们保存为空条目,我得到了 Json 类似 "car": {"id": 545234, "version": 0}
的响应。我发现没有 Jackson 注释可以忽略空对象,只有空或 null 属性。
员工实体具有以下形式:
@Entity
public class Employee {
@Id
@GeneratedValue
private Long id;
@Version
private Long version;
private String name;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "car_id")
@JsonManagedReference
private Car car;
.
. Desk, getters and setters
.
}
和OneToOne Reference的另一面
@Entity
public class Car{
@Id
@GeneratedValue
private Long id;
@Version
private Long version;
private String name;
@OneToOne(fetch = FetchType.LAZY, mappedBy = "employee")
@JsonBackReference
private Employee employee;
.
. getters and setters
.
}
例如,我将此作为 post 操作发送到我的服务
{
"name": "ACME",
.
.
.
"employee": {
"name": "worker 1",
"car": {},
"desk": {
floor: 3,
number: 4,
phone: 444
}
.
.
.
},
"addresses": [],
"building": {},
.
.
.
}
我得到了保存的数据作为响应
{
"id": 34534,
"version": 0,
"name": "ACME",
.
.
.
"employee": {
"id": 34535,
"version":0,
"name": "worker 1",
"car": {"id": 34536, "version": 0},
"desk": {
"id": 34538,
"version":0,
"floor": 3,
"number": 4,
"phone": 444
}
.
.
.
},
"addresses": [],
"building": {"id": 34539, "version": 0},
.
.
.
}
正如在响应中看到的那样,我得到了带有 id、版本、许多空值和空字符串的空 table 行,因为当我保存(持久化)主要反序列化公司时 class,另一个实体也被保存,因为它们被注释为级联。
我发现了很多像
我尝试了以下方法,我写了一个 BeanDeserializerModifier
并尝试在标准 beandeserializer 上包装一个自己的反序列化器:
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public List<BeanPropertyDefinition> updateProperties(DeserializationConfig config,
BeanDescription beanDesc,
List<BeanPropertyDefinition> propDefs) {
logger.debug("update properties, beandesc: {}", beanDesc.getBeanClass().getSimpleName());
return super.updateProperties(config, beanDesc, propDefs);
}
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
JsonDeserializer<?> deserializer) {
logger.debug("modify deserializer {}",beanDesc.getBeanClass().getSimpleName());
// This fails:
// return new DeserializationWrapper(deserializer, beanDesc);
return deserializer; // This works, but it is the standard behavior
}
});
这是包装器(和错误):
public class DeserializationWrapper extends JsonDeserializer<Object> {
private static final Logger logger = LoggerFactory.getLogger( DeserializationWrapper.class );
private final JsonDeserializer<?> deserializer;
private final BeanDescription beanDesc;
public DeserializationWrapper(JsonDeserializer<?> deserializer, BeanDescription beanDesc) {
this.deserializer = deserializer;
this.beanDesc = beanDesc;
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
logger.debug("deserialize in wrapper {} ",beanDesc.getBeanClass().getSimpleName());
final Object deserialized = deserializer.deserialize(p, ctxt);
ObjectCodec codec = p.getCodec();
JsonNode node = codec.readTree(p);
// some logig that not work
// here. The Idea is to detect with the json parser that the node is empty.
// If it is empty I will return null here and not the deserialized pojo
return deserialized;
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue) throws IOException {
logger.debug("deserializer - method 2");
intoValue = deserializer.deserialize(p, ctxt);
return intoValue;
}
@Override
public boolean isCachable() {
return deserializer.isCachable();
}
.
. I try to wrap the calls to the deserializer
.
Deserialization Wrapper 不工作并在第一次调用后崩溃 com.fasterxml.jackson.databind.exc.MismatchedInputException: No _valueDeserializer assigned at [Source: (PushbackInputStream); line: 2, column: 11] (through reference chain: ... Company["name"])
我的问题:有没有办法扩展工作标准反序列化器的行为,反序列化器在解析时检测到当前 jsonNode 为空且 return null而不是空的 class 实例?也许我的想法是错误的,还有完全不同的解决方案?
在Angular UI 方面解决它是没有选择的。我们使用 Jackson 2.9.5.
将 BeanDeserializerModifier
与自定义解串器一起使用是个好主意。您只需要改进反序列化器的实现。在您的示例中,问题出在这些行中:
final Object deserialized = deserializer.deserialize(p, ctxt); //1.
ObjectCodec codec = p.getCodec(); //2.
JsonNode node = codec.readTree(p); //3.
第 1.
行显示 JSON Object
。在行 2.
和 3.
中,您想创建一个 JsonNode
,但空的 JSON Object
已在行 1.
中读取。接下来的两行将尝试将其余的有效负载读取为 JsonNode
.
Jackson
默认使用 BeanDeserializer
反序列化常规 POJO
classes。我们可以扩展这个 class 并提供我们自己的实现。在版本 2.10.1
deserialise
中,方法如下所示:
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
// common case first
if (p.isExpectedStartObjectToken()) {
if (_vanillaProcessing) {
return vanillaDeserialize(p, ctxt, p.nextToken());
}
// 23-Sep-2015, tatu: This is wrong at some many levels, but for now... it is
// what it is, including "expected behavior".
p.nextToken();
if (_objectIdReader != null) {
return deserializeWithObjectId(p, ctxt);
}
return deserializeFromObject(p, ctxt);
}
return _deserializeOther(p, ctxt, p.getCurrentToken());
}
在大多数情况下,在不需要特殊处理的情况下,vanillaDeserialize
方法将被调用。让我们来看看:
private final Object vanillaDeserialize(JsonParser p, DeserializationContext ctxt, JsonToken t) throws IOException {
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.getCurrentName();
do {
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
}
return bean;
}
如您所见,它几乎完成了我们想要的一切,除了它甚至为空 JSON Object
创建新对象。它会在创建新对象后立即检查字段是否存在。一行太远了。不幸的是,这个方法是私有的,我们不能覆盖它。让我们把它复制到我们的 class 并稍微修改一下:
class EmptyObjectIsNullBeanDeserializer extends BeanDeserializer {
EmptyObjectIsNullBeanDeserializer(BeanDeserializerBase src) {
super(src);
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (_vanillaProcessing) {
return vanillaDeserialize(p, ctxt);
}
return super.deserialize(p, ctxt);
}
private Object vanillaDeserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
p.nextToken();
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
String propName = p.getCurrentName();
do {
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
return bean;
}
return null;
}
}
您可以像下面这样注册:
class EmptyObjectIsNullBeanDeserializerModifier extends BeanDeserializerModifier {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass() == Car.class) { //TODO: change this condition
return new EmptyObjectIsNullBeanDeserializer((BeanDeserializerBase) deserializer);
}
return super.modifyDeserializer(config, beanDesc, deserializer);
}
}
简单 POC
:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.Data;
import java.io.File;
import java.io.IOException;
public class EmptyObjectIsNullApp {
public static void main(String[] args) throws IOException {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
SimpleModule emptyObjectIsNullModule = new SimpleModule();
emptyObjectIsNullModule.setDeserializerModifier(new EmptyObjectIsNullBeanDeserializerModifier());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(emptyObjectIsNullModule);
Wrapper wrapper = mapper.readValue(jsonFile, Wrapper.class);
System.out.println(wrapper);
}
}
@Data
class Wrapper {
private Car car;
}
@Data
class Car {
private int id;
}
对于“空对象”JSON
负载:
{
"car": {}
}
以上代码打印:
Wrapper(car=null)
对于 JSON
具有某些字段的负载:
{
"car": {
"id": 1
}
}
以上代码打印:
Wrapper(car=Car(id=1))