Deserialize Lists and Objects to the same Structure with Jackson - java

As input for my Application I might get either a single JsonObject, or a List of them:
input1 = [ { "prop": "val1" }, { "prop": "val2" } ]
input2 = { "prop": "val" }
I can use JsonNode as target type for both inputs
objectMapper.readValue(input1, JsonNode.class);
objectMapper.readValue(input2, JsonNode.class);
And then evaluate whether the root node is a ArrayNode or ObjectNode.
I seek a way to define my custom target type, like a List<MyObject> which has one Element if a JsonObject is provided, or zero to multiple, if a List is provided.
objectMapper.readValue(input, new TypeRef<ArrayList<MyObject>>() {});
however fails for the single object - it can not construc an Array-Type from {.
I was trying to create my own type:
public class MyList extends ArrayList<MyObject> {
public String prop;
#JsonCreator
public MyList(String prop) {
super();
this.prop = prop; // Resp add(new MyObject(prop));
}
public MyList() {}
}
But Jackson refuses to use the JsonCreator for single objects.
Is there any way, I could do that (ideally without a custom serializer, unless that one can be made pretty generic)

Of course, Jackson has an easy solution for that:
DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY to your help!
#EqualsAndHashCode
public class Example {
#JsonProperty public String name
}
#Test
public void experiment() {
ObjectMapper om = new ObjectMapper();
om.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
String list= "[{ \"name\": \"peter\" }]";
String single= "{ \"name\": \"peter\" }";
List<Example> respList = om.readValue(list, new TypeReference<List<Example>>() {});
List<Example> respSingle = om.readValue(single, new TypeReference<List<Example>>() {});
Assert.assertEquals(respList, respSingle)
}

Related

Jackson Deserialize array or single object [duplicate]

I'm new with java and objectMapper. I'm trying to parse json field that is possible that a key have two types, it could be a string or array.
examples:
{
"addresses": [],
"full_name": [
"test name_1",
"test name_2"
],
}
or
{
{
"addresses": [],
"full_name": "test name_3",
}
}
Class example:
#JsonIgnoreProperties(ignoreUnknown = true)
#Data -> lombok.Data
public class Document {
private List<String> addresses;
#JsonProperty("full_name")
private String fullName;
}
I used objectMapper to deserialize json, works correctly when the 'full_name' field has a string but when arrive an array fail deserialization.
The idea is that when arrive a string put value in attribute but when arrive array, concatenate de array elements as string (String.join(",", value))
It's possible to apply custom deserialization in a class method? For example setFullName() (use lombok.Data)
I saw others examples in this site, but not work.
Thank's for all
From jackson 2.6 you can use JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY
#JsonProperty("full_name")
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private String[] fullName;
Elaborating on #Deadpool answer, you can use setter which accept the array and then join it to string:
#JsonProperty("full_name")
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
void setFullName(String[] name)
{
this.fullName = String.join(",", name);
}
Both answers are great. I just want to mention about custom Deserializer.
You can easily extend from StdDeserializer<Document> and override deserialize method:
public class DocumentDeserializer extends StdDeserializer<Document> {
#Override
public Document deserialize(JsonParser p, DeserializationContext ctxt, Document value) throws IOException {
JsonNode root = p.getCodec().readTree(p);
JsonNode node = root.get("full_name");
if(node.isArray()) {
//get array data from node iterator then join as String and
//call setFirstName
}
return value;
}
}
Then don't forget to call registerModule of ObjectMapper to register your deserializer

ObjectMapper convert to arraylist not preserving order

I have a json like:
{ "results": [
{
"message_id": "1"
},
{
"message_id": "2"
}
]}
When i use jackson object mapper the json returned to my collection doesnt preserve the order of messageid_1 and message id_2.
Following is my Objectmapper code:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.readValue(json, Response.class);
Following is my Response.class
public class Response implements Serializable {
#JsonProperty("results")
LinkedList<Object> results;
public LinkedList<Object> getResults() {
return results;
}
public void setResults(LinkedList<Object> results) {
this.results = results;
}
}
Please help me a way to preserve the order of json objects in the array[Not the property order ,i meant objects order]
In version 2.11.2 the order of JSON is respected

ObjectMapper readValue

I load a ressource file json
with the text format
{
"sources": [{
"prop1": "1",
"prop2": "2"
},
{
"prop1": "1",
"prop2": "2"
},
],
"redirection": [{
"prop1": "1",
"prop2": "2"
}
]
}
I have a class with this properties prop1 and prop2
I want to recover with ObjectMapper a list class. What the method ?
This code doesn't work ....
Map<String, Object> mp = mapper.readValue(jsonResource.getInputStream(),new TypeReference<Map<String, Object>>() {});
String sourceText= new ObjectMapper().readTree(jsonResource.getInputStream()).get("sources").asText();
mapper.readValue(sourceText, new TypeReference<List<MyClass>>(){});
Thanks for your help
In your case, I would write a custom JsonDeserializer. Haven't really tested the code, but I think the idea is clear:
final MyClassDeserializer myClassDeserializer = new MyClassDeserializer();
final SimpleModule deserializerModule = new SimpleModule();
deserializerModule.addDeserializer(MyClass.class, myClassDeserializer);
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(deserializerModule);
And the code for JsonDeserializer:
public class MyClassDeserializer extends JsonDeserializer<MyClass> {
#Override
public MyClass deserialize(final JsonParser jsonParser, final DeserializationContext context)
throws IOException {
final JsonNode node = jsonParser.getCodec().readTree(jsonParser);
final JsonNode sourcesNode = node.get("sources");
if(node.isArray()) {
final ArrayNode arrayNode = (ArrayNode) node;
final Iterable<JsonNode> nodes = arrayNode::elements;
final Set<Source> set = StreamSupport.stream(nodes.spliterator(), false)
.map(mapper)
.collect(Collectors.toSet());
...
}
...
}
First thing: Your JSON is invalid. There is a comma after the second object in the sources array. This has to be deleted.
Second: I think you didn't choose the right type for your result. What your JSON represents is a map which maps from string to an array of objects. So the type should be something like Map<String, Props[]> (Since you didn't provide the name of your class, I called it Props.
With these considerations you can construct a MapType by using ObjectMappers getTypeFactory() method and deserialize the value using the constructed type like shown below.
ObjectMapper mapper = new ObjectMapper();
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, Props[].class);
Map<String, Props[]> map = mapper.readValue(s, mapType);
I actually voted for the other answer, but this is my idea, to create the classes and let jackson do the work :
public class ResourceTest {
#Test
public void test1() throws IOException {
assertTrue(true);
Resource resource = new Resource();
resource.getRedirectrions().add(makeRedirectrion("rprop11", "rprop12"));
resource.getRedirectrions().add(makeRedirectrion("rprop21", "rprop22"));
resource.getSources().add(makeSource("sprop11","sprop12"));
resource.getSources().add(makeSource("sprop21","sprop22"));
String json = new ObjectMapper().writeValueAsString(resource);
System.out.println(json);
Resource resource1 = new ObjectMapper().readValue(json, Resource.class);
System.out.println(resource1);
}
private Source makeSource(String prop1, String prop2) {
Source source = new Source();
source.setProp1(prop1);
source.setProp2(prop2);
return source;
}
private Redirectrion makeRedirectrion(String prop1, String prop2) {
Redirectrion redirectrion = new Redirectrion();
redirectrion.setProp1(prop1);
redirectrion.setProp2(prop2);
return redirectrion;
}
}
Output is:
{"sources":[{"prop1":"sprop11","prop2":"sprop12"},{"prop1":"sprop21","prop2":"sprop22"}],"redirectrions":[{"prop1":"rprop11","prop2":"rprop12"},{"prop1":"rprop21","prop2":"rprop22"}]}
Resource{sources=[Source{prop1='sprop11', prop2='sprop12'}, Source{prop1='sprop21', prop2='sprop22'}], redirectrions=[Source{prop1='rprop11', prop2='rprop12'}, Source{prop1='rprop21', prop2='rprop22'}]}

How Jackson-Creating a JsonObject

I want to create a JsonObject like this:
{
Response: 200,
Lists: [
{
Test: "Math",
Result: "6",
Credit: "3"
},
{
Test: "C++",
Result: "10",
Credit: "6"
}
]
}
I know create this with lib org.json but with Jackson? i try to use
JsonNodeFactory nodeFactory = new JsonNodeFactory();
but i have this problem
The constructor JsonNodeFactory() is not visible
Make sure to use the latest version of Jackson. They moved from codehaus to FasterXML: http://wiki.fasterxml.com/JacksonHome.
You don't need to instantiate the factory. You can use the public static one: com.fasterxml.jackson.databind.node.JsonNodeFactory.instance.
JsonNodeFactory factory = JsonNodeFactory.instance;
ObjectNode root = factory.objectNode();
root.put("Response", 200);
ArrayNode list = factory.arrayNode();
list.add(...);
...
root.set("List", list);
Note that Jackson is a great library to map Java POJOs to JSON (and back). Rather than creating the JSON structure by hand, you can create Java classes that Jackson will serialize to JSON:
public class Item {
#JsonProperty("Test")
private String test;
#JsonProperty("Result")
private String result;
#JsonProperty("Credit")
private String credit;
}
public class Root {
#JsonProperty("Response")
private int response;
#JsonProperty("List")
private List<Item> list;
}
public static void main(String[] args) {
Root root = new Root();
...
String json = new ObjectMapper().writeValueAsString(root)
}
To create a JsonNode object use ObjectMapper. For example:
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readValue(JSON_STRING, JsonNode.class)
Refer to the Jackson documentation for information.

How do I configure the the jackson objectmapper to correctly deserialize to pojo?

I'm having a bit of a problem understanding how i should configure the objectMapper and pojo when deserializing. My Json is created by another application that
supports both xml and json. It returns a list with myobject, but the Json contains the type, like this:
[
{
"myobject": {
"somethingcool": "amazing",
"contactPersonsForMyObject": [
"test.test#gmail.com",
"test#test.se"
],
"myObjectId": "c85e48730501bfae41e67714c6131b7d"
}
},
{
"myobject": {
"somethingcool": "cool",
"contactPersonsForMyObject": [
"test.test2#gmail.com",
"test#test2.se"
],
"myObjectId": "c85e48730501bfae41e67714cqwerty"
}
}
]
My class:
public class MyObject {
private String myObjectId;
private String somethingcool;
private List<String> contactPersonsForMyObject;
public String getMyObjectId() {
return myObjectId;
}
public void setMyObjectId(String myObjectId) {
this.myObjectId = myObjectId;
}
public String getSomethingcool() {
return somethingcool;
}
public void setSomethingcool(String somethingcool) {
this.somethingcool = somethingcool;
}
public List<String> getContactPersonsForMyObject() {
return contactPersonsForMyObject;
}
public void setContactPersonsForMyObject(List<String> contactPersonsForMyObject) {
this.contactPersonsForMyObject = contactPersonsForMyObject;
}
}
But when doing:
List<MyObject> myObjects = mapper.convertValue(rootNode, new TypeReference<List<MyObject>>() {});
I'm getting a exception stating:
java.lang.IllegalArgumentException: Unrecognized field "myobject" (Class com.domain.MyObject), not marked as ignorable
at [Source: N/A; line: -1, column: -1] (through reference chain: com.domain.MyObject["myobject"])
It's like the mapper do not understand the extra "layer".
When serializing to get this structure it is possible to configure the mapper like this: mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
So there should be somehow to do the reverse?
Thank you!
You need to give it concrete classes and not interfaces. So
List<Map<String, MyObject>> myObjects = mapper.readValue(json, new TypeReference<ArrayList<HashMap<String, MyObject>>>() {
});
What you need is to use #JsonTypeInfo annotation on type (class), which will include additional type information. In your case it looks as if you wanted to include a type id as property key.
If so, inclusion method should be "as wrapper object", and you will also need to define what type id of "myobject" binds to -- this can be done by adding #JsonTypeName("myobject") for MyObject class (it needs to be included in subtype of whatever has #JsonTypeInfo, but in this case both would be added for the same class).
Your json has an extra level of nesting: you have a list of Maps of Strings to MyObjects, not a List of MyObjects. You'd need to read it like this:
List<Map<String, MyObject>> myObjects = mapper.readValue(json, new TypeReference<List<Map<String, MyObject>>>() {
});
Or else change whatever is generating this json to ditch the inner Map (IMHO that'd be better).
Change List<String> to ArrayList<String>
and then
MyObject myObject = mapper.readValue(json, MyObject.class);
Add the following constructor to MyObject class
#JsonCreator
public MyObject(#JsonProperty("myObjectId") String myObjectId,
#JsonProperty("somethingcool") String somthingcool,
#JsonProperty("contact") ArrayList<String> contactPersonsForMyObject) {
this.myObjectID = myObjectId;
this.somethingcool = somethingcool;
this.contactPersonsForMyObject = contactPersonsForMyObject;
}
and change the return value for the getter to ArrayList

Categories

Resources