Jackson反序列化SNS消息报错MismatchedInputException
Jackson deserialization SNS message error MismatchedInputException
我正在编写一项功能,通过 SNS HTTP 请求处理来自 Amazon Simple Email Service 的回调。我想将亚马逊提供的消息解析为本地对象结构。问题是 SNS 将 JSON 消息包装成 String 并且 Jackson 无法解析它。我收到一个错误:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `xxx.email.domain.aws.ses.Notification` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"notificationType":"Delivery","mail":{"timestamp":"2019-10-02T14:43:14.570Z" ... next values of the message ... }}')
来自 SNS 的整条消息如下所示:
{
"Type" : "Notification",
"MessageId" : "4944xxxx-711d-57d4-91b8-8215cxxxxx",
"TopicArn" : "arn:aws:sns:eu-west-1:...",
"Message" : "{\"notificationType\":\"Delivery\",\"mail\":{\"timestamp\":\"2019-10-02T14:43:14.570Z\", ... next values of the message ... },\"delivery\":{\"timestamp\":\"2019-10-02T14:43:16.030Z\", ... next values of the message ... }}",
"Timestamp" : "2019-10-02T14:43:16.062Z",
"SignatureVersion" : "1",
"Signature" : "signature base64",
"SigningCertURL" : "cert url",
"UnsubscribeURL" : "unsubscribe url"
}
我的实际本地结构如下所示:
@Data
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class MessageWrapper {
private String type;
private String messageId;
private String topicArn;
private Notification message;
private Date timestamp;
private String signatureVersion;
private String signature;
private String signingCertURL;
private String unsubscribeURL;
}
@Data
public class Notification {
private String notificationType;
private Mail mail;
}
@Data
public class Mail {
private String messageId;
private String source;
private String sourceArn;
private String sourceIp;
private String sendingAccountId;
private String[] destination;
}
我正在寻找一些方法来告诉 Jackson Message
应该从一个字符串中提取出来并被当作一个普通的 JSON。
编辑
反序列化
private MessageWrapper deserializeMessage(String message) throws IOException {
return new ObjectMapper().readValue(message, MessageWrapper.class);
}
我认为要解决这个问题,您需要为 MessageWrapper
class 中的 Notification
字段以及 Mail
字段中的 Mail
字段自定义反序列化器=12=] class 像下面这样:
public class NotificationDeserializer extends JsonDeserializer<Notification> {
@Override
public Notification deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String text = p.getText();
return new ObjectMapper().readValue(text, Notification.class);
}
}
public class MailDeserializer extends JsonDeserializer<Mail> {
@Override
public Mail deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String text = p.getText();
return new ObjectMapper().readValue(text, Mail.class);
}
}
在您的 class 上添加一些注释,如下所示:
@Data
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class MessageWrapper {
private String type;
private String messageId;
private String topicArn;
@JsonDeserialize(using = NotificationDeserializer.class)
private Notification message;
private Date timestamp;
private String signatureVersion;
private String signature;
private String signingCertURL;
private String unsubscribeURL;
}
@Data
public class Notification {
private String notificationType;
@JsonDeserialize(using = MailDeserializer.class)
private Mail mail;
}
@Data
public class Mail {
private String messageId;
private String source;
private String sourceArn;
private String sourceIp;
private String sendingAccountId;
private String[] destination;
}
编辑 1
实际上不需要MailDeserializer
。 NotificationDeserializer
单独解决了这个问题。
编辑 2
必须在自定义反序列化器中使用新的 ObjectMapper
。
message
属性 是 Notification
类型,Jackson
期望 JSON Object
而不是 string value
。在这种情况下,您可以创建自定义反序列化器或使用某种环回实现来实现通用解决方案。如果给定的有效载荷不是 JSON Object
将其读取为 String
并使用此 String
.
再次调用反序列化
要避免 WhosebugError
,您需要使用 ObjectMapper
的另一个实例或使用 BeanDeserializerModifier
保留 BeanDeserializer
实例并在遇到 JSON Object
的地方使用它.简单示例如下所示:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
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.module.SimpleModule;
import com.fasterxml.jackson.databind.node.TextNode;
import lombok.Data;
import lombok.ToString;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.Objects;
import java.util.Set;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
SimpleModule loopBackModule = new SimpleModule();
loopBackModule.setDeserializerModifier(new LoopBackBeanDeserializerModifier(Collections.singleton(Notification.class)));
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(loopBackModule);
MessageWrapper wrapper = mapper.readValue(jsonFile, MessageWrapper.class);
System.out.println(wrapper.getMessage());
}
}
class LoopBackBeanDeserializerModifier extends BeanDeserializerModifier {
private final Set<Class> allowedClasses;
LoopBackBeanDeserializerModifier(Set<Class> allowedClasses) {
this.allowedClasses = Objects.requireNonNull(allowedClasses);
}
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (allowedClasses.contains(beanDesc.getBeanClass())) {
return new LoopBackBeanDeserializer<>((BeanDeserializerBase) deserializer);
}
return deserializer;
}
}
class LoopBackBeanDeserializer<T> extends BeanDeserializer {
private final BeanDeserializerBase baseDeserializer;
protected LoopBackBeanDeserializer(BeanDeserializerBase src) {
super(src);
this.baseDeserializer = src;
}
@Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// if first token is VALUE_STRING we should read it as String and
// run deserialization process again based on this String.
if (p.currentToken() == JsonToken.VALUE_STRING) {
return (T) ((ObjectMapper) p.getCodec()).readValue(p.getText(), _valueClass);
}
// vanilla bean deserialization
return (T) baseDeserializer.deserialize(p, ctxt);
}
}
POJO
型号相同。您只需要列出您预计会出现问题的 类,loop-back
机制将适用于它们。
我正在编写一项功能,通过 SNS HTTP 请求处理来自 Amazon Simple Email Service 的回调。我想将亚马逊提供的消息解析为本地对象结构。问题是 SNS 将 JSON 消息包装成 String 并且 Jackson 无法解析它。我收到一个错误:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `xxx.email.domain.aws.ses.Notification` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"notificationType":"Delivery","mail":{"timestamp":"2019-10-02T14:43:14.570Z" ... next values of the message ... }}')
来自 SNS 的整条消息如下所示:
{
"Type" : "Notification",
"MessageId" : "4944xxxx-711d-57d4-91b8-8215cxxxxx",
"TopicArn" : "arn:aws:sns:eu-west-1:...",
"Message" : "{\"notificationType\":\"Delivery\",\"mail\":{\"timestamp\":\"2019-10-02T14:43:14.570Z\", ... next values of the message ... },\"delivery\":{\"timestamp\":\"2019-10-02T14:43:16.030Z\", ... next values of the message ... }}",
"Timestamp" : "2019-10-02T14:43:16.062Z",
"SignatureVersion" : "1",
"Signature" : "signature base64",
"SigningCertURL" : "cert url",
"UnsubscribeURL" : "unsubscribe url"
}
我的实际本地结构如下所示:
@Data
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class MessageWrapper {
private String type;
private String messageId;
private String topicArn;
private Notification message;
private Date timestamp;
private String signatureVersion;
private String signature;
private String signingCertURL;
private String unsubscribeURL;
}
@Data
public class Notification {
private String notificationType;
private Mail mail;
}
@Data
public class Mail {
private String messageId;
private String source;
private String sourceArn;
private String sourceIp;
private String sendingAccountId;
private String[] destination;
}
我正在寻找一些方法来告诉 Jackson Message
应该从一个字符串中提取出来并被当作一个普通的 JSON。
编辑
反序列化
private MessageWrapper deserializeMessage(String message) throws IOException {
return new ObjectMapper().readValue(message, MessageWrapper.class);
}
我认为要解决这个问题,您需要为 MessageWrapper
class 中的 Notification
字段以及 Mail
字段中的 Mail
字段自定义反序列化器=12=] class 像下面这样:
public class NotificationDeserializer extends JsonDeserializer<Notification> {
@Override
public Notification deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String text = p.getText();
return new ObjectMapper().readValue(text, Notification.class);
}
}
public class MailDeserializer extends JsonDeserializer<Mail> {
@Override
public Mail deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String text = p.getText();
return new ObjectMapper().readValue(text, Mail.class);
}
}
在您的 class 上添加一些注释,如下所示:
@Data
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class MessageWrapper {
private String type;
private String messageId;
private String topicArn;
@JsonDeserialize(using = NotificationDeserializer.class)
private Notification message;
private Date timestamp;
private String signatureVersion;
private String signature;
private String signingCertURL;
private String unsubscribeURL;
}
@Data
public class Notification {
private String notificationType;
@JsonDeserialize(using = MailDeserializer.class)
private Mail mail;
}
@Data
public class Mail {
private String messageId;
private String source;
private String sourceArn;
private String sourceIp;
private String sendingAccountId;
private String[] destination;
}
编辑 1
实际上不需要MailDeserializer
。 NotificationDeserializer
单独解决了这个问题。
编辑 2
必须在自定义反序列化器中使用新的 ObjectMapper
。
message
属性 是 Notification
类型,Jackson
期望 JSON Object
而不是 string value
。在这种情况下,您可以创建自定义反序列化器或使用某种环回实现来实现通用解决方案。如果给定的有效载荷不是 JSON Object
将其读取为 String
并使用此 String
.
要避免 WhosebugError
,您需要使用 ObjectMapper
的另一个实例或使用 BeanDeserializerModifier
保留 BeanDeserializer
实例并在遇到 JSON Object
的地方使用它.简单示例如下所示:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
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.module.SimpleModule;
import com.fasterxml.jackson.databind.node.TextNode;
import lombok.Data;
import lombok.ToString;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.Objects;
import java.util.Set;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
SimpleModule loopBackModule = new SimpleModule();
loopBackModule.setDeserializerModifier(new LoopBackBeanDeserializerModifier(Collections.singleton(Notification.class)));
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(loopBackModule);
MessageWrapper wrapper = mapper.readValue(jsonFile, MessageWrapper.class);
System.out.println(wrapper.getMessage());
}
}
class LoopBackBeanDeserializerModifier extends BeanDeserializerModifier {
private final Set<Class> allowedClasses;
LoopBackBeanDeserializerModifier(Set<Class> allowedClasses) {
this.allowedClasses = Objects.requireNonNull(allowedClasses);
}
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (allowedClasses.contains(beanDesc.getBeanClass())) {
return new LoopBackBeanDeserializer<>((BeanDeserializerBase) deserializer);
}
return deserializer;
}
}
class LoopBackBeanDeserializer<T> extends BeanDeserializer {
private final BeanDeserializerBase baseDeserializer;
protected LoopBackBeanDeserializer(BeanDeserializerBase src) {
super(src);
this.baseDeserializer = src;
}
@Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// if first token is VALUE_STRING we should read it as String and
// run deserialization process again based on this String.
if (p.currentToken() == JsonToken.VALUE_STRING) {
return (T) ((ObjectMapper) p.getCodec()).readValue(p.getText(), _valueClass);
}
// vanilla bean deserialization
return (T) baseDeserializer.deserialize(p, ctxt);
}
}
POJO
型号相同。您只需要列出您预计会出现问题的 类,loop-back
机制将适用于它们。