杰克逊反序列化SNS消息错误MismatchedInputException

时间:2019-10-02 15:06:37

标签: java amazon-web-services jackson aws-sdk amazon-sns

我正在编写一种通过SNS HTTP请求处理来自Amazon Simple Email Service的回调的功能。我想将Amazon提供的消息解析为本地对象结构。问题是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);
}

2 个答案:

答案 0 :(得分:3)

我认为要解决此问题,您需要为Notification类的MessageWrapper字段和Mail类的Notification字段使用一个自定义反序列化器,类似于以下内容:

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); 
    }
}

在类中带有一些注释,如下所示:

@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

答案 1 :(得分:1)

message属性的类型为NotificationJackson期望JSON Object而不是string value。在那种情况下,您可以创建自定义解串器或使用某种环回实现来实现通用解决方案。如果给定的有效载荷不是JSON Object,则将其读取为String,然后使用此String再次调用反序列化。

为避免使用StackOverflowError,您需要使用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机制就可以解决这些问题。

相关问题