I have many servlets like this:
public class DoSmthServlet extends AbstractDTOServlet<DoSmthServlet.Params> {
#Override
public Response service(Params params) throws Exception {
// Some logic...
Response response = new Response();
response.field1 = data1;
response.field2 = data2;
// ...
return response;
}
public static class Params implements DomainDTO {
public long arg1;
public boolean arg2;
// ...
}
public static class Response implements DomainDTO {
public String field1;
public long field2;
// ...
}
}
I need to fill Response object with data, but it can contain really many fields. How to do it without writing many response.fieldN = dataN in each servlet? I don't want to write constructors for each class because I'll still have to write such assignments in constructors.
Maybe there is any library that can do it, or any pattern that I can use, or any way to generate constructors for Response classes?
Maybe Dozer bean mapper can help you. Some example:
Mapper mapper = new DozerBeanMapper();
DestinationObject destObject =
mapper.map(sourceObject, DestinationObject.class);
or
DestinationObject destObject = new DestinationObject();
mapper.map(sourceObject, destObject);
I don't know any library that can do it for you. But... you can use fluent builder which will simplify Response creation and make it a little bit... easier I think.
ex.
public class ResponseBuilder {
private Response response = new Response();
public ResponseBuilder withField1(String field1) {
response.field1 = field1;
return this;
}
public ResponseBuilder withField2(String field2) {
response.field2 = field2;
return this;
}
public Response build() {
return response;
}
}
// usage
Response response = Response.builder.withField1("a").withField2("b").build();
BTW: I would rather avoid writing constructor with many arguments for a Value Object/DTO class, because every argument passed to constructor should be considered as required.
If number of fields are same for all the Response object then write one static function and call it from different servlets. static function will do all response.fieldN = dataN
Related
I have two apis which is return following json responses.
I created a class called 'Card' and how should I achieve 'expiry' field which have different type for particular request.
Thanks in advance.
For simplicity i'll work only with card object from example, i won't be wrapping it another object. To avoid duplication in examples, i'll use this base class to hold common fields:
public abstract class BaseCard {
private String brand;
private String fundingMethod;
private String scheme;
//setters and getters
}
You have few options.
Option 1:
If you know which api you are getting response from, you can have two classes, each holding expiry in format specific for the api. When getting data from api 1 use:
public class CardApi1 extends BaseCard {
private String expiry;
//setters and getters
}
And for api 2:
public class CardApi2 extends BaseCard {
private Expiry expiry;
//setters and getters
}
The expiry object looks like this:
public class Expiry {
private String month;
private String year;
//setters and getters
}
If you are calling api 1, deserialize to CardApi1, else to CardApi2.
Option 2: Make the expiry field Object, this way anything can be deserialized into it.
public class CardApiMixed extends BaseCard {
private Object expiry;
//setters and getters
public String getExpiryAsString() {
return (String) this.expiry;
}
public Map<String, Object> getExpiryAsMap() {
#SuppressWarnings("unchecked")
Map<String, Object> expiry = (Map<String, Object>) this.expiry;
return expiry;
}
}
This way no matter which api you are calling, you deserialize into one class. The downside is, when retrieving expiry, you still have to know which api the data comes from, so you use correct method.
Option 3: Write custom deserializer which will resolve the field correctly, no matter which api is used. Personally i would go for this option. The deserializer is this:
public class ExpiryResolvingCardDeserializer extends StdDeserializer<CardApiResolved> {
public ExpiryResolvingCardDeserializer() {
super(CardApiResolved.class);
}
#Override
public CardApiResolved deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode node = parser.getCodec().readTree(parser);
CardApiResolved card = new CardApiResolved();
card.setBrand(node.get("brand").asText());
card.setFundingMethod(node.get("fundingMethod").asText());
card.setScheme(node.get("scheme").asText());
JsonNode expiryNode = node.get("expiry");
Expiry expiry = new Expiry();
if (expiryNode.isObject()) {
//that's for api 2
expiry.setMonth(expiryNode.get("month").asText());
expiry.setYear(expiryNode.get("year").asText());
} else {
//for api 1
String text = expiryNode.asText();
//assuming format is always mmYY, you can handle it in differently if there are other options
int splitIndex = 2;
expiry.setMonth(text.substring(0, splitIndex));
expiry.setYear(text.substring(splitIndex));
}
card.setExpiry(expiry);
return card;
}
}
To summarise it, when expiry node is object, handle it like an object to get month and year data from it, if it is a string, split it to extract month and year. Like this no matter the format, expiry is always resolved to Expiry class. The card class will look like this:
#JsonDeserialize(using = ExpiryResolvingCardDeserializer.class)
public class CardApiResolved extends BaseCard {
private Expiry expiry;
//setters and getters
}
Note the JsonDeserialize annotation specifying the deserializer to use for this type. And lastly, some unit tests to play with and check results. The api responses are files in the test resources.
public class CardApiTests {
private final ObjectMapper mapper = new ObjectMapper();
#Test
public void testCardApi1() throws Exception {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-1.json");
//map to CardApi1, when calling api 1
CardApi1 result = this.mapper.readValue(inputStream, CardApi1.class);
assertEquals("0139", result.getExpiry());
}
#Test
public void testCardApi2() throws Exception {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-2.json");
//map to CardApi2, when calling api 2
CardApi2 result = this.mapper.readValue(inputStream, CardApi2.class);
assertEquals("1", result.getExpiry().getMonth());
assertEquals("39", result.getExpiry().getYear());
}
#Test
public void testCardApiMixed_Api1() throws Exception {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-1.json");
CardApiMixed result = this.mapper.readValue(inputStream, CardApiMixed.class);
assertEquals("0139", result.getExpiryAsString());
}
#Test
public void testCardApiMixed_Api2() throws Exception {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-2.json");
CardApiMixed result = this.mapper.readValue(inputStream, CardApiMixed.class);
assertEquals("1", result.getExpiryAsMap().get("month"));
assertEquals("39", result.getExpiryAsMap().get("year"));
}
#Test
public void testCardApiResolved_Api1() throws Exception {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-1.json");
CardApiResolved result = this.mapper.readValue(inputStream, CardApiResolved.class);
assertEquals("01", result.getExpiry().getMonth());
assertEquals("39", result.getExpiry().getYear());
}
#Test
public void testCardApiResolved_Api2() throws Exception {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-2.json");
CardApiResolved result = this.mapper.readValue(inputStream, CardApiResolved.class);
assertEquals("1", result.getExpiry().getMonth());
assertEquals("39", result.getExpiry().getYear());
}
}
Can you please share how the expiry field is prepared? like
"expiery":"0139" = month+year.
if the field is prepared like this then you can keep variable type either String or list, you have needed a minor modification. You will give an idea according to the given example.
public class App {
private static final HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.connectTimeout(Duration.ofSeconds(10))
.build();
public static void main(String[] args) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create("https://jsonmock.abc.com/api/articles?page=3"))
.setHeader("User-Agent", "Java 11 HttpClient Bot") // add request header
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
// print response headers
HttpHeaders headers = response.headers();
headers.map().forEach((k, v) -> System.out.println(k + ":" + v));
String responseBody = response.body();
ObjectMapper mapper = new ObjectMapper();
Object newJsonNode = mapper.readValue(responseBody, Object.class);
Map<String, Object> objectMap = (Map) newJsonNode;
if (objectMap.get("expiry") instanceof List) {
//if I assume that expiry string created by appending month and year
List<Map<String, Object>> list = (List) objectMap.get("expiry");
Map<String, Object> map = list.get(0);
String exp = (String) map.get("month") + (String) map.get("year");
objectMap.put("expiry", exp);
//then parse to direct object
} else if (objectMap.get("expiry") instanceof String) {
//then cast to dto
//you can apply reverse logic here, i.e string to map
}
}
}
i have the following method
public static <E> APIGatewayProxyResponseEvent generateResponse(E request, E response, int statusCode){
JSONObject result = new JSONObject();
result.put(Constants.REQUEST, request);
result.put(Constants.RESPONSE, response);
return new APIGatewayProxyResponseEvent()
.withBody(result.toString())
.withStatusCode(statusCode)
.withHeaders(Constants.commonHeaders);
}
i am getting net.sf.json.JSONException: java.lang.reflect.InvocationTargetException when result.put(Constants.RESPONSE, response); is executed
response is
Also the corresponding class is:
public class PhysicalMediaURL extends MediaURL {
private static final String IDENTIFIER_PREFIX = "images/I/";
public PhysicalMediaURL(String physicalId, String extension, MediaHostnameProvider mediaHostnameProvider) {
super("images/I/" + physicalId, extension, mediaHostnameProvider);
}
}
public abstract class MediaURL implements URL {
private final String identifier;
private final String extension;
private final MediaHostnameProvider mediaHostnameProvider;
public MediaURL(String identifier, String extension, MediaHostnameProvider mediaHostnameProvider) {
this.identifier = identifier;
this.extension = extension;
this.mediaHostnameProvider = mediaHostnameProvider;
}
public String getIdentifier() {
return this.identifier;
}
public String getExtension() {
return this.extension;
}
public String getDomainName() {
return this.mediaHostnameProvider.getMediaHostname(this.getExtension());
}
public String getURL() {
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.append("https://");
urlBuilder.append(this.getDomainName());
urlBuilder.append('/');
urlBuilder.append(this.getIdentifier());
urlBuilder.append('.');
urlBuilder.append(this.getExtension());
return urlBuilder.toString();
}
public List<String> getStyleTags() {
return null;
}
}
where PhysicalMediaURL is of type: URL and that is an interface
public interface URL {
String getIdentifier();
String getDomainName();
String getExtension();
List<String> getStyleTags();
String getURL();
}
I am a bit stuck in this.. need help.
First off it looks like you are using a JSON implementation that is not updated as regularly as the other ones(Your exception is from net.sf.json). I always recommend using the org.json implementation as it receives regular updates and bugfixes.
Most implementations of JSONObject, when used in this form, use bean based reflection to retrieve values from your object. This is not always what you want when your object is in an inheritance hierarchy because, depending on the object and the JSONObject impl, it will pull fields from the implementation that are not on your higher level type(URL in this case).
If you really want a generic serialization function use something like Jackson or Gson that will allow you to specify the type as a part of the serialization. Otherwise consider transforming your objects, before they are passed to your generateResponse function, into simpler objects such as a Map<String, String> that can serialize unambiguously.
As a final thought JSONObject's generic serialization works, but, its performance is likely to be worse than using a dedicated higher level serializer like Jackson. It's best used with the explicit put methods to generate simple objects.
I have some nested classes in Java, simplified here. Getters and setters exist.
Example
public class Planet {
#JsonProperty("name")
private String name;
#JsonProperty("moons")
private List<Moon> moons;
}
public class Moon {
#JsonProperty("moonname")
private String name;
#JsonProperty("craters")
private int craters;
}
I want to be able to deserialize the records on mongo (following this same structure) to java objects on the rest controller, specifically the HTTP GET request.
#RestController
#RequestMapping("/planets")
public class PlanetController {
#Autowired
private PlanetService planetService;
#RequestMapping("/")
public List<Planet> getAllPlanets() {
//Need to deserialize here
return planetService.getAll();
}
#RequestMapping("/{name}")
public Planet getItemsWithName(#PathVariable("name") String name) {
//deserialize here
return planetService.getEntryWithName(name.toLowerCase());
}
PlanetService.getAll() is expecting return type of List. getEntryWithName() is expecting return type of Planet.
How can I loop the results in the getAll() so I can deserialize them before they are returned?
Using Jackson's object mapper, I can do the serialization of a Java object to a JSON object.
ObjectMapper mapper = new ObjectMapper();
try {
mapper.writeValue(new File("target/mars.json"), mars);
} catch (IOException e) {
e.printStackTrace();
}
I can probably use readValue for the opposite process but I don't know how to loop the results.
I will appreciate the help. Let me know if something is not clear.
public List<Planet> getAllPlanets() {
List<Planet> planets = planetService.getAll();
String jsonString = new ObjectMapper().writeValueAsString(planets);
return planets;
}
I have a Packet class, which serializes and deserializes fine:
#JsonDeserialize(builder = Packet.Builder.class)
public final class Packet {
// Constructors, getters, etc
// ...
public static final class Builder {
// Builder variables...
// ...
#JsonProperty("value_date")
public Builder valueDate(String val) {
valueDate = val;
return this;
}
#JsonProperty("generation_date")
public Builder generationTimeStamp(String val) {
generationTimeStamp = val;
return this;
}
public Packet build() { return new Packet(this); }
}
I also have a PacketResponse object, which looks like:
#JsonDeserialize(builder = PacketResponse.Builder.class)
public final class PacketResponse {
// Packet and response message
private final String message;
private final Packet packet;
// Constructors, getters, etc
// ...
public static final class Builder {
private Packet packet;
private String message;
private Builder() {}
#JsonProperty("packet")
public Builder packet(Packet val) {
packet = val;
return this;
}
#JsonProperty("message")
public Builder message(String val) {
message = val;
return this;
}
public PacketResponse build() {
return new PacketResponse(this);
}
}
The serialization of PacketResponse objects works fine, and results in:
{
"message": "the quick brown fox",
"packet": {
"valueDate": "2015-10-24",
"generationTimeStamp": "2015-11-12T20:45:24+0000"
}
}
However, when I try to deserialize this JSON back into a PacketResponse object, using
new ObjectMapper().readValue(json, PacketResponse.class);
I get an Unrecognized field "valueDate" (class com.m.Packet$Builder), not marked as ignorable.
It seems like it's trying to deserialize Packet but because it isn't doing it directly Jackson can't see the #JsonProperty("value_date") annotation and instead is looking for a valueDate field (since that's the name of the builder method).
Can anyone help me figure out the right way to deserialize the JSON? I'm still new to Jackson and don't really know how to resolve this.
The JSON contains "valueDate" but the #JsonProperty annotation specifies "value_date" (with an underscore).
Ditto "generation_date" in the annotation vs "generationTimeStamp" in the JSON.
I am trying to include raw JSON inside a Java object when the object is (de)serialized using Jackson. In order to test this functionality, I wrote the following test:
public static class Pojo {
public String foo;
#JsonRawValue
public String bar;
}
#Test
public void test() throws JsonGenerationException, JsonMappingException, IOException {
String foo = "one";
String bar = "{\"A\":false}";
Pojo pojo = new Pojo();
pojo.foo = foo;
pojo.bar = bar;
String json = "{\"foo\":\"" + foo + "\",\"bar\":" + bar + "}";
ObjectMapper objectMapper = new ObjectMapper();
String output = objectMapper.writeValueAsString(pojo);
System.out.println(output);
assertEquals(json, output);
Pojo deserialized = objectMapper.readValue(output, Pojo.class);
assertEquals(foo, deserialized.foo);
assertEquals(bar, deserialized.bar);
}
The code outputs the following line:
{"foo":"one","bar":{"A":false}}
The JSON is exactly how I want things to look. Unfortunately, the code fails with an exception when attempting to read the JSON back in to the object. Here is the exception:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.StringReader#d70d7a; line: 1, column: 13] (through reference chain: com.tnal.prism.cobalt.gather.testing.Pojo["bar"])
Why does Jackson function just fine in one direction but fail when going the other direction? It seems like it should be able to take its own output as input again. I know what I'm trying to do is unorthodox (the general advice is to create an inner object for bar that has a property named A), but I don't want to interact with this JSON at all. My code is acting as a pass-through for this code -- I want to take in this JSON and send it back out again without touching a thing, because when the JSON changes I don't want my code to need modifications.
Thanks for the advice.
EDIT: Made Pojo a static class, which was causing a different error.
#JsonRawValue is intended for serialization-side only, since the reverse direction is a bit trickier to handle. In effect it was added to allow injecting pre-encoded content.
I guess it would be possible to add support for reverse, although that would be quite awkward: content will have to be parsed, and then re-written back to "raw" form, which may or may not be the same (since character quoting may differ).
This for general case. But perhaps it would make sense for some subset of problems.
But I think a work-around for your specific case would be to specify type as 'java.lang.Object', since this should work ok: for serialization, String will be output as is, and for deserialization, it will be deserialized as a Map. Actually you might want to have separate getter/setter if so; getter would return String for serialization (and needs #JsonRawValue); and setter would take either Map or Object. You could re-encode it to a String if that makes sense.
Following #StaxMan answer, I've made the following works like a charm:
public class Pojo {
Object json;
#JsonRawValue
public String getJson() {
// default raw value: null or "[]"
return json == null ? null : json.toString();
}
public void setJson(JsonNode node) {
this.json = node;
}
}
And, to be faithful to the initial question, here is the working test:
public class PojoTest {
ObjectMapper mapper = new ObjectMapper();
#Test
public void test() throws IOException {
Pojo pojo = new Pojo("{\"foo\":18}");
String output = mapper.writeValueAsString(pojo);
assertThat(output).isEqualTo("{\"json\":{\"foo\":18}}");
Pojo deserialized = mapper.readValue(output, Pojo.class);
assertThat(deserialized.json.toString()).isEqualTo("{\"foo\":18}");
// deserialized.json == {"foo":18}
}
}
I was able to do this with a custom deserializer (cut and pasted from here)
package etc;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
/**
* Keeps json value as json, does not try to deserialize it
* #author roytruelove
*
*/
public class KeepAsJsonDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
TreeNode tree = jp.getCodec().readTree(jp);
return tree.toString();
}
}
Use it by annotating the desired member like this:
#JsonDeserialize(using = KeepAsJsonDeserializer.class)
private String value;
#JsonSetter may help. See my sample ('data' is supposed to contain unparsed JSON):
class Purchase
{
String data;
#JsonProperty("signature")
String signature;
#JsonSetter("data")
void setData(JsonNode data)
{
this.data = data.toString();
}
}
This is a problem with your inner classes. The Pojo class is a non-static inner class of your test class, and Jackson cannot instantiate that class. So it can serialize, but not deserialize.
Redefine your class like this:
public static class Pojo {
public String foo;
#JsonRawValue
public String bar;
}
Note the addition of static
Adding to Roy Truelove's great answer, this is how to inject the custom deserialiser in response to appearance of #JsonRawValue:
import com.fasterxml.jackson.databind.Module;
#Component
public class ModuleImpl extends Module {
#Override
public void setupModule(SetupContext context) {
context.addBeanDeserializerModifier(new BeanDeserializerModifierImpl());
}
}
import java.util.Iterator;
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
public class BeanDeserializerModifierImpl extends BeanDeserializerModifier {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
Iterator<SettableBeanProperty> it = builder.getProperties();
while (it.hasNext()) {
SettableBeanProperty p = it.next();
if (p.getAnnotation(JsonRawValue.class) != null) {
builder.addOrReplaceProperty(p.withValueDeserializer(KeepAsJsonDeserialzier.INSTANCE), true);
}
}
return builder;
}
}
This easy solution worked for me:
public class MyObject {
private Object rawJsonValue;
public Object getRawJsonValue() {
return rawJsonValue;
}
public void setRawJsonValue(Object rawJsonValue) {
this.rawJsonValue = rawJsonValue;
}
}
So I was able to store raw value of JSON in rawJsonValue variable and then it was no problem to deserialize it (as object) with other fields back to JSON and send via my REST. Using #JsonRawValue didnt helped me because stored JSON was deserialized as String, not as object, and that was not what I wanted.
This even works in a JPA entity:
private String json;
#JsonRawValue
public String getJson() {
return json;
}
public void setJson(final String json) {
this.json = json;
}
#JsonProperty(value = "json")
public void setJsonRaw(JsonNode jsonNode) {
// this leads to non-standard json, see discussion:
// setJson(jsonNode.toString());
StringWriter stringWriter = new StringWriter();
ObjectMapper objectMapper = new ObjectMapper();
JsonGenerator generator =
new JsonFactory(objectMapper).createGenerator(stringWriter);
generator.writeTree(n);
setJson(stringWriter.toString());
}
Ideally the ObjectMapper and even JsonFactory are from the context and are configured so as to handle your JSON correctly (standard or with non-standard values like 'Infinity' floats for example).
Here is a full working example of how to use Jackson modules to make #JsonRawValue work both ways (serialization and deserialization):
public class JsonRawValueDeserializerModule extends SimpleModule {
public JsonRawValueDeserializerModule() {
setDeserializerModifier(new JsonRawValueDeserializerModifier());
}
private static class JsonRawValueDeserializerModifier extends BeanDeserializerModifier {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
builder.getProperties().forEachRemaining(property -> {
if (property.getAnnotation(JsonRawValue.class) != null) {
builder.addOrReplaceProperty(property.withValueDeserializer(JsonRawValueDeserializer.INSTANCE), true);
}
});
return builder;
}
}
private static class JsonRawValueDeserializer extends JsonDeserializer<String> {
private static final JsonDeserializer<String> INSTANCE = new JsonRawValueDeserializer();
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return p.readValueAsTree().toString();
}
}
}
Then you can register the module after creating the ObjectMapper:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonRawValueDeserializerModule());
String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}";
Pojo deserialized = objectMapper.readValue(json, Pojo.class);
I had the exact same issue.
I found the solution in this post :
Parse JSON tree to plain class using Jackson or its alternatives
Check out the last answer.
By defining a custom setter for the property that takes a JsonNode as parameter and calls the toString method on the jsonNode to set the String property, it all works out.
Using an object works fine both ways... This method has a bit of overhead deserializing the raw value in two times.
ObjectMapper mapper = new ObjectMapper();
RawJsonValue value = new RawJsonValue();
value.setRawValue(new RawHello(){{this.data = "universe...";}});
String json = mapper.writeValueAsString(value);
System.out.println(json);
RawJsonValue result = mapper.readValue(json, RawJsonValue.class);
json = mapper.writeValueAsString(result.getRawValue());
System.out.println(json);
RawHello hello = mapper.readValue(json, RawHello.class);
System.out.println(hello.data);
RawHello.java
public class RawHello {
public String data;
}
RawJsonValue.java
public class RawJsonValue {
private Object rawValue;
public Object getRawValue() {
return rawValue;
}
public void setRawValue(Object value) {
this.rawValue = value;
}
}
I had a similar problem, but using a list with a lot of JSON itens (List<String>).
public class Errors {
private Integer status;
private List<String> jsons;
}
I managed the serialization using the #JsonRawValue annotation. But for deserialization I had to create a custom deserializer based on Roy's suggestion.
public class Errors {
private Integer status;
#JsonRawValue
#JsonDeserialize(using = JsonListPassThroughDeserialzier.class)
private List<String> jsons;
}
Below you can see my "List" deserializer.
public class JsonListPassThroughDeserializer extends JsonDeserializer<List<String>> {
#Override
public List<String> deserialize(JsonParser jp, DeserializationContext cxt) throws IOException, JsonProcessingException {
if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
final List<String> list = new ArrayList<>();
while (jp.nextToken() != JsonToken.END_ARRAY) {
list.add(jp.getCodec().readTree(jp).toString());
}
return list;
}
throw cxt.instantiationException(List.class, "Expected Json list");
}
}