Saheb
Saheb

Reputation: 1492

Decode base64 encoded JSON to POJO with jackson and spring-boot

I have a request coming like this

{
    "varA"  : "A",
    "varB" : "TCFNhbiBKb3NlMRgwFgYDVQQK"
}

where key varB is a base64 encoded JSON string. something like this:

{
    "nestedVarB1": "some value here",
    "nestedVarB2" : "some other value here"
}

I want to convert it into some POJOs like this:

@Data
public class MyRequest {
    private String varA;
    private B varB;
}

@Data
public class B {
    private String nestedVarB1;
    private String nestedVarB2;
}

I went through several solutions on stack overflow like this and this, but I want to convert the JSON directly into an object of type MyRequest maybe by writing some sort of Jackson deserializer.

How can I convert the JSON directly into MyRequest using Jackson and Spring Boot?

Note:

  1. POJOs MyRequest and B are very big and B could be further nested, so doing it manually would be a big task.
  2. I don't have any control over the request coming, it is coming from a third party source. So, can't change the format of the request.

Upvotes: 4

Views: 14570

Answers (1)

I've done some experiments and here is a simple Jackson Deserializer which should work for you.

Deserializer implements the ContextualDeserializer interface to get access to actual bean property (for example varB). It's necessary for detecting correct result type, because deserializer itself can be attached to a field of any type.

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;

import java.io.IOException;
import java.util.Base64;

public class Base64Deserializer extends JsonDeserializer<Object> implements ContextualDeserializer {

    private Class<?> resultClass;

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property) throws JsonMappingException {
        this.resultClass = property.getType().getRawClass();
        return this;
    }

    @Override
    public Object deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
        String value = parser.getValueAsString();
        Base64.Decoder decoder = Base64.getDecoder();

        try {
            ObjectMapper objectMapper = new ObjectMapper();
            byte[] decodedValue = decoder.decode(value);

            return objectMapper.readValue(decodedValue, this.resultClass);
        } catch (IllegalArgumentException | JsonParseException e) {
            String fieldName = parser.getParsingContext().getCurrentName();
            Class<?> wrapperClass = parser.getParsingContext().getCurrentValue().getClass();

            throw new InvalidFormatException(
                parser,
                String.format("Value for '%s' is not a base64 encoded JSON", fieldName),
                value,
                wrapperClass
            );
        }
    }
}

Here is an example of mapped class.

public class MyRequest {

    private String varA;

    @JsonDeserialize(using = Base64Deserializer.class)
    private B varB;

    public String getVarA() {
        return varA;
    }

    public void setVarA(String varA) {
        this.varA = varA;
    }

    public B getVarB() {
        return varB;
    }

    public void setVarB(B varB) {
        this.varB = varB;
    }
}

Upvotes: 6

Related Questions