Deserialise POJO array member from its string representation - java

I am looking for a generic way to deserialise with Jackson a JSON such as:
{
"hello": "baby",
"eyes": "[blue,green]"
}
To a POJO such as
public class Whatever {
#Setter private String hello;
#Setter private List<Color> eyes;
}
With Color being an enum.
When I try naively like below:
ObjectMapper mapper = new ObjectMapper();
mapper.convertValue(properties, objectClass);
I get the error
Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token
Obviously this is because Jackson can only deserialise arrays from JSON arrays, not their string representation.
I tried to activate ACCEPT_SINGLE_VALUE_AS_ARRAY but it would consider the value of the property "eyes" to be an array with a single String element "[blue,green]" (which fails to convert to the enumeration Color)
Any hint would be very much appreciated.

The problem isn't that ACCEPT_SINGLE_VALUE_AS_ARRAY is causing the "eyes" property to be interpreted as an array with a single element, that option allows Jackson to coerce types so that
{
"hello": "baby",
"eyes": "[blue,green]"
}
would be interpreted the same as
[{
"hello": "baby",
"eyes": "[blue,green]"
}]
This way single elements can be used with Java's Collections more information can be found at
http://fasterxml.github.io/jackson-databind/javadoc/2.0.0/com/fasterxml/jackson/databind/DeserializationFeature.html#ACCEPT_SINGLE_VALUE_AS_ARRAY
As far as your problem goes, the best option would be to have the JSON submitted with color as a JSON array like:
{
"hello": "baby",
"eyes": [
"blue",
"green",
]
}
Otherwise you may need to have your Whatever class have the #JsonSetter annotation on a setEyes method with String parameter where you parse the String to manually create the list of Color yourself.
#JsonSetter
public void setEyes(final String eyes) {
// Parse string and set field here
}

Related

Converting JSON Array to Java Array always throws empty values

So I have this variable specCifDetailsReturn which contains the ff. payload
[
{"ax21:cHType":"S",
"ax21:cardNumber":4***********7126,"ax21:returnCde":"00",
"ax21:cancelCode":"",
"ax21:vipCode":"",
"ax21:custrNbr":"0*****3426"},
{"ax21:cHType":"S",
"ax21:cardNumber":4***********3038,"ax21:returnCde":"00",
"ax21:cancelCode":"H",
"ax21:vipCode":"",
"ax21:custrNbr":"0*****3426"}
]
And the ff. Model Class to extract the params I need from the Array
#Data
#AllArgsConstructor
#NoArgsConstructor
#JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public final class SpecCifInfo {
#JsonAlias("ax21:cHType")
private String cHType;
#JsonAlias("ax21:cardNumber")
private String cardNumber;
}
I am trying to convert it to a Java ArrayList so that I could loop into it and find a card number. But for some reason it always throws a null value on the log even though the specCifDetailsReturn variable has a value. Below is the snippet of my code.
Gson gson = new Gson();
Type type = new TypeToken<List<SpecCifInfo>>(){}.getType();
ArrayList<SpecCifInfo> specDetails = gson.fromJson(specCifDetailsReturn.toString(),type);
for (SpecCifInfo specInfo : specDetails){
LOGGER.debug("Spec CIF Details", specInfo.getCHType() + "-" + specInfo.getCardNumber());
}
Sample Output of the SpecCifInfo Object that has null values
Those annotations are for the Jackson library, and you are manually using Gson. You should either keep them and just let Spring handle the deserialization for you by specifying a List<SpecCifInfo> parameter in the controller method, or you should use GSON's #SerializedName annotation. Either way will work.

Deserializing an array that can contain multiple types

I have some JSON that can hold an array of both arrays of maps, or just maps (I know this seems dumb). I'm having trouble identifying what the ArrayOrMap class should look like to allow deserialisation of this structure.
{
"example": [
[
{
"key1": "value1"
},
{
"key2": "value2"
}
],
{
"key3": "value3"
}
]
}
I'm trying to deserialise this using ObjectMapper, and Jackson's #JsonCreator annotations:
#Getter
static class StackOverflowExample {
private final ArrayOrMap[] example;
#JsonCreator
StackOverflowExample (
#JsonProperty("example") ArrayOrMap[] example,
) {
this.example = example;
}
}
#Getter
static class ArrayOrMap {
???
}
...
// code that attempts to deserialise
final StackOverflowExample result = objectMapper.readValue(
data,
StackOverflowExample.class
);
I'm trying to deserialise the JSON into an array of objects, each of which can be either an array of maps, or just a map, but I don't know what the class for ArrayOrMap should look like. It seems like object deserialisation always requires a key of some kind, but here I'm just deserialising an array of objects, so I don't have a key to look at.
I'm aware that the structure of this JSON is pretty poor to begin with, but changing it now would take significant effort.
Any help is appreciated, thanks!

java jackson parse json array

I need help with parsing, I've tried to create a different kind of model classes, but no use, please help me out here. the json looks like this:
[
[
1518909300000,
"0.08815700",
"0.08828700",
"0.08780000",
"0.08792900",
"1727.93100000",
1518910199999,
"152.11480375",
5118,
"897.71600000",
"79.04635703",
"0"
],
[
1518910200000,
"0.08788400",
"0.08824200",
"0.08766200",
"0.08810000",
"1789.81300000",
1518911099999,
"157.20177729",
6201,
"898.89500000",
"78.95697080",
"0"
]
]
and I'm trying to parse it using data class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class KlineResponse {
public List<Kline> getKlineList() {
return klineList;
}
public List<Kline> klineList;
public class Kline {
#JsonProperty("4")
Double close;
#JsonProperty("8")
Integer tradesNumber;
public Double getClose() {
return close;
}
public void setClose(Double close) {
this.close = close;
}
public Integer getTradesNumber() {
return tradesNumber;
}
public void setTradesNumber(Integer tradesNumber) {
this.tradesNumber = tradesNumber;
}
}
}
and this line
mapper.readValue(response.getBody(), new TypeReference<List<KlineResponse>>(){})
or
mapper.readValue(response.getBody(), KlineResponse.class)
but each time the error:
Can not deserialize instance of pt.settings.model.KlineResponse out of START_ARRAY token,
please help
The core issue is that you receive an array of arrays where you expect and array of objects. Changing mapper.readValue(response.getBody(), KlineResponse.class) to mapper.readValue(response.getBody(), Object[].class) confirms it.
You have a couple of options on how to proceed:
Change from Jackson to standard JSON parsing, as suggested by #cricket_007 on his answer
Instead of mapping it to an object try to access the JSON differently. See #jschnasse's answer for an example.
Change the format of text you parse, if you can
If you can't change the format of the input then you can either
Create a constructor and annotate it with #JsonCreator, like instructed here
Parse the input as Object array and feed the parsed array into a constructor of your own
You don't need any java classes. There are no JSON objects to deserialize, only arrays.
In the second case, Jackson is expecting { "klineList": [] }
In the first, [{ "klineList": [] }, { "klineList": [] }]
And a Kline object is only parsable as {"4": 0.0, "8": 0 } (replace zeros with any value of same type)... So really unclear why you expected that to work given that data... The annotations are not the index of the lists.
Plus, your lists have both strings and integers, so you can only deserialize as TypeReference<List<List<Object>>>, then iterate that to parse ints, floats, or strings
I might recommend you use a standard json parser, not an objectmapper
Use JsonNode together with JPointer. Avoid to create a POJO and work directly on the data via JsonNode.
ObjectMapper mapper = new ObjectMapper();
JsonNode matrix = mapper.readValue(in, JsonNode.class);
matrix.forEach(array -> {
System.out.println("Next Values:");
System.out.println(array.at("/4").asDouble());
System.out.println(array.at("/8").asInt());
});
Prints
Next Values:
0.087929
5118.0
Next Values:
0.0881
6201.0

Polymorphic Jackson deserialization: getting either a string or an array of strings

My question is pretty much identical to this one, except that I'm using Java/Jackson instead of C#:
In C# how can I deserialize this json when one field might be a string or an array of strings?
My input JSON can be this:
{ "foo": "a string" }
or this:
{ "foo": ["array", "of", "strings" ] }
My class looks like this:
class MyClass {
public List<String> foo;
}
If the input contains a single string, I want it to become the first entry in the list.
How can I deserialize foo using Jackson? I could write a custom deserializer, which I've done before, but I thought there might be an easier way.
There is a feature called ACCEPT_SINGLE_VALUE_AS_ARRAY which is turned off by default but you can turn it on:
objectMapper = new ObjectMapper()
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
You can also turn it on per case:
class SomeClass {
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<String> items;
// ...
}

Configure XStream to dynamically map to different objects

I am hitting a RESTful 3rd party API that always sends JSON in the following format:
{
"response": {
...
}
}
Where ... is the response object that needs to be mapped back to a Java POJO. For instance, sometimes the JSON will contain data that should be mapped back to a Fruit POJO:
{
"response": {
"type": "orange",
"shape": "round"
}
}
...and sometimes the JSON will contain data that should be mapped back to an Employee POJO:
{
"response": {
"name": "John Smith",
"employee_ID": "12345",
"isSupervisor": "true",
"jobTitle": "Chief Burninator"
}
}
So depending on the RESTful API call, we need these two JSON results mapped back to one of the two:
public class Fruit {
private String type;
private String shape;
// Getters & setters for all properties
}
public class Employee {
private String name;
private Integer employeeId;
private Boolean isSupervisor;
private String jobTitle;
// Getters & setters for all properties
}
Unfortunately, I cannot change the fact that this 3rd party REST service always sends back a { "response": { ... } } JSON result. But I still need a way to configure a mapper to dynamically map such a response back to either a Fruit or an Employee.
First, I tried Jackson with limited success, but it wasn't as configurable as I wanted it to be. So now I am trying to use XStream with its JettisonMappedXmlDriver for mapping JSON back to POJOs. Here's the prototype code I have:
public static void main(String[] args) {
XStream xs = new XStream(new JettisonMappedXmlDriver());
xs.alias("response", Fruit.class);
xs.alias("response", Employee.class);
// When XStream sees "employee_ID" in the JSON, replace it with
// "employeeID" to match the field on the POJO.
xs.aliasField("employeeID", Employee.class, "employee_ID");
// Hits 3rd party RESTful API and returns the "*fruit version*" of the JSON.
String json = externalService.getFruit();
Fruit fruit = (Fruit)xs.fromXML(json);
}
Unfortunately when I run this I get an exception, because I have xs.alias("response", ...) mapping response to 2 different Java objects:
Caused by: com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$UnknownFieldException: No such field me.myorg.myapp.domain.Employee.type
---- Debugging information ----
field : type
class : me.myorg.myapp.domain.Employee
required-type : me.myorg.myapp.domain.Employee
converter-type : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
path : /response/type
line number : -1
version : null
-------------------------------
So I ask: what can I do to circumvent the fact that the API will always send back the same "wrapper" response JSON object? The only thing I can think of is first doing a String-replace like so:
String json = externalService.getFruit();
json = json.replaceAll("response", "fruit");
...
But this seems like an ugly hack. Does XStream (or another mapping framework) provide anything that would help me out in this particular case? Thansk in advance.
There are two ways with Jackson:
test manually that the wanted keys are there (JsonNode has the necessary methods);
use JSON Schema; there is one API in Java: json-schema-validator (yes, that is mine), which uses Jackson.
Write a schema matching your first object type:
{
"type": "object",
"properties": {
"type": {
"type": "string",
"required": true
},
"shape": {
"type": "string",
"required": true
}
},
"additionalProperties": false
}
Load this as a schema, validate your input against it: if it validates, you know you need to deserialize against your fruit class. Otherwise, make the schema for the second item type, validate against it as a security measure, and deserialize using the other class.
There are code examples for the API, too (version 1.4.x)
If you do know the actual type, it should be relatively straight-forward with Jackson.
You need to use a generic wrapper type like:
public class Wrapper<T> {
public T response;
}
and then the only trick is to construct type object to let Jackson know what T there is.
If it is statically available, you just do:
Wrapper<Fruit> wrapped = mapper.readValue(input, new TypeReference<Wrapper<Fruit>>() { });
Fruit fruit = wrapped.response;
but if it is more dynamically generated, something like:
Class<?> rawType = ... ; // determined using whatever logic is needed
JavaType actualType = mapper.getTypeFactory().constructGenericType(Wrapper.class, rawType);
Wrapper<?> wrapper = mapper.readValue(input, actualType);
Object value = wrapper.response;
but either way it "should just work". Note that in latter case you may be able to use base types ("? extends MyBaseType"), but in general dynamic type can't be specified.

Categories

Resources