How to Deserialize Array of JSON Objects in Java - java

So I'm used to getting JSON objects from a given API/endpoint, e.g.:
{
"count": 5,
"results": [
{
"example": "test",
"is_valid": true
},
{
"example": "test2",
"is_valid": true
}
]
}
And in a custom deserializer that extends com.fasterxml.jackson.databind.deser.std.StdDeserializer, I know I can use the JsonParser object like so to get the base node to work off of, i.e.:
#Override
public ResultExample deserialize(JsonParser jp, DeserializationContext ctxt) {
JsonNode node = jp.getCodec().readTree(jp);
JsonNode count = node.get("count");
// code to put parsed objects into a ResultExample object...
}
However, I just encountered an API that simply returns an array of JSON objects, e.g.:
[
{
"example": "test",
"is_valid": true
},
{
"example": "test2",
"is_valid": true
},
]
So, I don't believe I can just parse this the same way as before. What would be the correct way to parse this using Jackson?

This may help you:
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Test {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
String json = "[\r\n" + " {\r\n" + " \"example\": \"test\",\r\n" + " \"is_valid\": true\r\n"
+ " },\r\n" + " {\r\n" + " \"example\": \"test2\",\r\n" + " \"is_valid\": true\r\n"
+ " }\r\n" + "]";
Example[] ex = mapper.readValue(json, Example[].class);
}
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({ "example", "is_valid" })
class Example {
#JsonProperty("example")
private String example;
#JsonProperty("is_valid")
private Boolean isValid;
public String getExample() {
return example;
}
#JsonProperty("example")
public void setExample(String example) {
this.example = example;
}
#JsonProperty("is_valid")
public Boolean getIsValid() {
return isValid;
}
#JsonProperty("is_valid")
public void setIsValid(Boolean isValid) {
this.isValid = isValid;
}
}

When response is a JSON Object you can use default bean deserialiser. In case it is a JSON Array you can read it as array and create response object manually. Below you can find example custom deserialiser and BeanDeserializerModifier which is used to register custom deserialiser. BeanDeserializerModifier allows to use default deserialiser when JSON payload fits POJO model:
import com.fasterxml.jackson.annotation.JsonProperty;
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.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
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.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.CollectionType;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass() == Response.class) {
return new ResponseJsonDeserializer((BeanDeserializerBase) deserializer);
}
return super.modifyDeserializer(config, beanDesc, deserializer);
}
});
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
System.out.println(mapper.readValue(jsonFile, Response.class));
}
}
class ResponseJsonDeserializer extends BeanDeserializer {
private final BeanDeserializerBase baseDeserializer;
protected ResponseJsonDeserializer(BeanDeserializerBase src) {
super(src);
this.baseDeserializer = src;
}
#Override
public Response deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (p.currentToken() == JsonToken.START_OBJECT) {
return (Response) baseDeserializer.deserialize(p, ctxt);
}
if (p.currentToken() == JsonToken.START_ARRAY) {
CollectionType collectionType = ctxt.getTypeFactory().constructCollectionType(List.class, Item.class);
JsonDeserializer<Object> deserializer = ctxt.findRootValueDeserializer(collectionType);
List<Item> results = (List<Item>) deserializer.deserialize(p, ctxt);
Response response = new Response();
response.setCount(results.size());
response.setResults(results);
return response;
}
throw MismatchedInputException.from(p, Response.class, "Only object or array!");
}
}
class Response {
private int count;
private List<Item> results;
// getters, setters, toString
}
class Item {
private String example;
#JsonProperty("is_valid")
private boolean isValid;
// getters, setters, toString
}
Above code for JSON Object payload prints:
Response{count=5, results=[Item{example='test', isValid=true}, Item{example='test2', isValid=true}]}
And for JSON Array payload prints:
Response{count=2, results=[Item{example='test', isValid=true}, Item{example='test2', isValid=true}]}

Guess I should have just written the unit test before asking the question, but apparently you can just do it the same way. Only difference is the base node is a JsonArray which you have to iterate over. Thanks everyone who looked into this.

Related

Serialize/de-serialize java duration to JSON with custom hh:mm format with Jackson

Description
I'm new to Java AND Jackson and I try to save a java.time.duration to a JSON in a nice and readable hh:mm (hours:minutes) format for storing and retrieving.
In my project I use:
Jackson com.fasterxml.jackson.core:jackson-databind:2.14.1.
Jackson com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.1 for the support of the newer Java 8 time/date classes.
Minimum working example:
Consider following example class:
public class Book {
private Duration timeToComplete;
public Book(Duration durationToComplete) {
this.timeToComplete = durationToComplete;
}
// default constructor + getter & setter
}
If I try to serialize a book instance into JSON like in the following code section
public class JavaToJson throws JsonProcessingException {
public static void main(String[] args) {
// create the instance of Book, duration 01h:11min
LocalTime startTime = LocalTime.of(13,30);
LocalTime endTime = LocalTime.of(14,41);
Book firstBook = new Book(Duration.between(startTime, endTime));
// create the mapper, add the java8 time support module and enable pretty parsing
ObjectMapper objectMapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.build()
.enable(SerializationFeature.INDENT_OUTPUT);
// serialize and print to console
System.out.println(objectMapper.writeValueAsString(firstBook));
}
}
it gives me the duration in seconds instead of 01:11.
{
"timeToComplete" : 4740.000000000
}
How would I change the JSON output into a hh:mm format?
What I tried until now
I thought about adding a custom Serializer/Deserializer (potentially a DurationSerializer?) during instantiation of the ObjectMapper but it seems I can't make the formatting work...
ObjectMapper objectMapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
// add the custom serializer for the duration
.addModule(new SimpleModule().addSerializer(new DurationSerializer(){
#Override
protected DurationSerializer withFormat(Boolean useTimestamp, DateTimeFormatter dtf, JsonFormat.Shape shape) {
// here I try to change the formatting
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("HH:mm");
return super.withFormat(useTimestamp, dtf, shape);
}
}))
.build()
.enable(SerializationFeature.INDENT_OUTPUT);
All it does is change it to this strange textual representation of the Duration:
{
"timeToComplete" : "PT1H11M"
}
So it seems I'm not completely off but the formatting is still not there. Maybe someone can help with the serializing/de-serializing?
Thanks a lot
hh:mm format is not supported by Jackson since Java does not recognise it by default. We need to customise serialisation/deserialisation mechanism and provide custom implementation.
Take a look at:
How to format a duration in java? (e.g format H:MM:SS)
Format a Milliseconds Duration to HH:MM:SS
Using some examples from linked articles I have created custom serialiser and deserialiser. They do not handle all possible cases but should do the trick for your requirements:
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.DurationSerializer;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DurationFormatUtils;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalTime;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class DurationApp {
public static void main(String[] args) throws JsonProcessingException {
LocalTime startTime = LocalTime.of(13, 30);
LocalTime endTime = LocalTime.of(14, 41);
Book firstBook = new Book(Duration.between(startTime, endTime));
// create the mapper, add the java8 time support module and enable pretty parsing
ObjectMapper objectMapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.addModule(new SimpleModule()
.addSerializer(Duration.class, new ApacheDurationSerializer())
.addDeserializer(Duration.class, new ApacheDurationDeserializer()))
.build()
.enable(SerializationFeature.INDENT_OUTPUT);
String json = objectMapper.writeValueAsString(firstBook);
System.out.println(json);
Book deserialisedBook = objectMapper.readValue(json, Book.class);
System.out.println(deserialisedBook);
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
class Book {
#JsonFormat(pattern = "HH:mm")
private Duration duration;
}
class ApacheDurationSerializer extends DurationSerializer {
private final String apachePattern;
public ApacheDurationSerializer() {
this(null);
}
ApacheDurationSerializer(String apachePattern) {
this.apachePattern = apachePattern;
}
#Override
public void serialize(Duration duration, JsonGenerator generator, SerializerProvider provider) throws IOException {
if (Objects.nonNull(apachePattern) && Objects.nonNull(duration)) {
String value = DurationFormatUtils.formatDuration(duration.toMillis(), apachePattern);
generator.writeString(value);
} else {
super.serialize(duration, generator, provider);
}
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
JsonFormat.Value format = findFormatOverrides(prov, property, handledType());
if (format != null && format.hasPattern() && isApacheDurationPattern(format.getPattern())) {
return new ApacheDurationSerializer(format.getPattern());
}
return super.createContextual(prov, property);
}
private boolean isApacheDurationPattern(String pattern) {
try {
DurationFormatUtils.formatDuration(Duration.ofDays(1).toMillis(), pattern);
return true;
} catch (Exception e) {
return false;
}
}
}
class ApacheDurationDeserializer extends DurationDeserializer {
private final String apachePattern;
private final int numberOfColonsInPattern;
public ApacheDurationDeserializer() {
this(null);
}
ApacheDurationDeserializer(String apachePattern) {
this.apachePattern = apachePattern;
this.numberOfColonsInPattern = countColons(apachePattern);
}
#Override
public Duration deserialize(JsonParser parser, DeserializationContext context) throws IOException {
if (Objects.nonNull(apachePattern)) {
String value = parser.getText();
if (this.numberOfColonsInPattern != countColons(value)) {
throw new JsonMappingException(parser, String.format("Pattern '%s' does not match value '%s'!", apachePattern, value));
}
if (numberOfColonsInPattern == 0) {
return Duration.ofSeconds(Long.parseLong(value.trim()));
}
String[] parts = value.trim().split(":");
return switch (parts.length) {
case 1 -> Duration.ofSeconds(Long.parseLong(value.trim()));
case 2 -> Duration.ofSeconds(TimeUnit.HOURS.toSeconds(Long.parseLong(parts[0]))
+ TimeUnit.MINUTES.toSeconds(Long.parseLong(parts[1])));
case 3 -> Duration.ofSeconds(TimeUnit.HOURS.toSeconds(Long.parseLong(parts[0]))
+ TimeUnit.MINUTES.toSeconds(Long.parseLong(parts[1]))
+ Long.parseLong(parts[2]));
default ->
throw new JsonMappingException(parser, String.format("Pattern '%s' does not match value '%s'!", apachePattern, value));
};
} else {
return super.deserialize(parser, context);
}
}
#Override
public Duration deserialize(JsonParser p, DeserializationContext ctxt, Duration intoValue) throws IOException {
return super.deserialize(p, ctxt, intoValue);
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType());
if (format != null && format.hasPattern() && isApacheDurationPattern(format.getPattern())) {
return new ApacheDurationDeserializer(format.getPattern());
}
return super.createContextual(ctxt, property);
}
private boolean isApacheDurationPattern(String pattern) {
try {
DurationFormatUtils.formatDuration(Duration.ofDays(1).toMillis(), pattern);
return true;
} catch (Exception e) {
return false;
}
}
private static int countColons(String apachePattern) {
return StringUtils.countMatches(apachePattern, ':');
}
}
Above code prints:
{
"duration" : "01:11"
}
Book(duration=PT1H11M)

How to dynamically convert JSON to XML with custom namespace?

I try to convert a JSON into an XML with the following code
final ObjectMapper objectMapper = new ObjectMapper();
final XmlMapper xmlMapper = new XmlMapper();
JsonNode jsonNode = objectMapper.readTree(jsonString);
String xmlString = xmlMapper
.writerWithDefaultPrettyPrinter()
.withRootName("rootname")
.writeValueAsString(jsonNode);
Basically it works. Does anyone know, how I can add a namespace to the serialized XML-attributes. I've no POJOs for the objects. The convert should generate from this
{
"Status" : "OK"
}
something like this:
<ns2:rootname xmlns:ns2="http://whatever-it-is.de/">
<ns2:state>OK</ns2:state>
</ns2:rootname>
just create a pojo and add jackson annotations, e.g.
#JacksonXmlProperty(localName="ns2:http://whatever-it-is.de/")
public class Status {
// ...
}
Or if you want to go without a pojo try a custom serializer which adds namespaces
https://www.baeldung.com/jackson-custom-serialization
You need to provide custom Json Node serialiser and use ToXmlGenerator. See below example:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import javax.xml.namespace.QName;
import java.io.IOException;
public class XmlMapperApp {
public static void main(String... args) throws Exception {
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.enable(SerializationFeature.INDENT_OUTPUT);
ObjectNode node = xmlMapper.createObjectNode()
.put("Status", "OK")
.set("node", xmlMapper.createObjectNode()
.put("int", 1)
.put("str", "str"));
SimpleModule module = new SimpleModule();
module.setSerializerModifier(new BeanSerializerModifier() {
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (beanDesc.getType().getRawClass().equals(ObjectNode.class)) {
return new ObjectNodeJsonSerializer(serializer);
}
return super.modifySerializer(config, beanDesc, serializer);
}
});
xmlMapper.registerModule(module);
System.out.println(xmlMapper.writeValueAsString(node));
}
}
class ObjectNodeJsonSerializer extends JsonSerializer<JsonNode> {
private final JsonSerializer baseSerializer;
ObjectNodeJsonSerializer(JsonSerializer baseSerializer) {
this.baseSerializer = baseSerializer;
}
#Override
public void serialize(JsonNode value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
ToXmlGenerator xmlGenerator = (ToXmlGenerator) gen;
xmlGenerator.setNextName(new QName("http://whatever-it-is.de/", "rootname", "anything"));
baseSerializer.serialize(value, gen, serializers);
}
}
Above example prints:
<wstxns1:rootname xmlns:wstxns1="http://whatever-it-is.de/">
<wstxns1:Status>OK</wstxns1:Status>
<wstxns1:node>
<wstxns1:int>1</wstxns1:int>
<wstxns1:str>str</wstxns1:str>
</wstxns1:node>
</wstxns1:rootname>
Underscore-java library can convert JSON to XML with namespace.
{
"ns2:rootname": {
"-xmlns:ns2": "http://whatever-it-is.de/",
"ns2:state": "OK"
},
"#omit-xml-declaration": "yes"
}
<ns2:rootname xmlns:ns2="http://whatever-it-is.de/">
<ns2:state>OK</ns2:state>
</ns2:rootname>

Map JSON string or JSON array to String in Java object [duplicate]

This question already has answers here:
Make Jackson interpret single JSON object as array with one element
(3 answers)
Deserializing json to pojo where json field has different data types
(2 answers)
Closed 1 year ago.
I have a JSON created by Elixir class which has a field which can be string or array:
field :logs, {:array, :string}
If anyone doesn't get this it will be like
{"logs" : "some log 1"}
or
{
"logs": ["some log 1", "some log 2"]
}
I have a Java field mapped for this:
#JsonProperty("logs")
private String logs;
This mapping works only when the logs comes as a String, but fails if the logs comes as array with error saying it will not be able to convert START_ARRAY to string.
How to serialize the field if it comes as array and store it as a comma separated string?
I see in tags that you use Jackson for parsing. This means you need to write and register with Jackson a custom deserializer for your logs field.
An example of such solution:
package tmp;
import java.io.IOException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ValueNode;
public class JacksonDemo {
public static class LogHolder {
#JsonProperty("logs")
#JsonDeserialize(using = ArrayOrStringJsonDeserializer.class)
private String logs;
#Override
public String toString() {
return "LogHolder(logs=" + logs + ")";
}
}
public static class ArrayOrStringJsonDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = (JsonNode) jsonParser.readValueAsTree();
if (node.isValueNode()) {
ValueNode valueNode = (ValueNode) node;
if (valueNode.isTextual()) {
return valueNode.textValue();
}
} else if (node.isArray()) {
ArrayNode arrayNode = (ArrayNode) node;
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(arrayNode.iterator(), Spliterator.ORDERED), false)
.map(JsonNode::textValue)
.collect(Collectors.joining(", "));
}
throw MismatchedInputException.from(jsonParser, String.class,
"Expected node to be of type String or array, but got " + node.getNodeType().toString());
}
}
public static void main(String args[]) throws Exception {
String[] docs = { "{\"logs\" : \"some log 1\"}", "{\"logs\": [\"some log 1\", \"some log 2\"]}" };
ObjectMapper om = new ObjectMapper();
for (String doc : docs) {
System.out.println(om.readValue(doc, LogHolder.class));
}
}
}
Result of executing this code:
LogHolder(logs=some log 1)
LogHolder(logs=some log 1, some log 2)

Trying to read values from array nested in JSON

I've been writing a Rest service using Jackson to extract the name and sizeFromStorage inside the response value.
I created the below classes in an attempt to do this:
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(value = {"status", "header"})
public class Vaults {
private List<Object> response;
public Vaults(){
}
public Vaults(List<Object> response){
this.response = response;
}
public List<Object> getResponse(){
return response;
}
public void setResponse(List<Object> response) {
this.response = response;
}
#Override
public String toString() {
return "" + response;
}
}
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import java.io.InputStream;
public class JacksonObjectModelTest {
public static void main(String[] args) throws IOException {
String jsonFileName = "/JsonRead/json.json";
List<Vaults> emps = new JacksonObjectModelTest().getVaultList(jsonFileName);
System.out.println(emps.toString());
}
public ArrayList<Vaults> getVaultList(String jsonFileName) throws IOException {
//read json file data to String
InputStream inputStream = getClass().getResourceAsStream(jsonFileName);
//create ObjectMapper instance
ObjectMapper objectMapper = new ObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
//convert json string to object
CollectionType collectionType = objectMapper.getTypeFactory().constructCollectionType(List.class, Vaults.class);
ArrayList<Vaults> emps = objectMapper.readValue(inputStream, collectionType);
return (ArrayList<Vaults>) emps;
}
}
{
"status": "ok",
"header": {
"now": 1491545894581,
"status": "ok",
"requestId": "WOcvJsCoAmoAAESbkBYAAAB5"
},
"response": {
"vault": [
{
"id": 20,
"name": "Apple",
"description": "",
"sizeFromStorage": 95957225298,
"storagePools": [
{
"storagePool": {
"id": 1,
"name": "storage-pool1",
"sizeFromStorage": 95957225298,
"generations": [
{
"generation": {
"sequence": 0
}
}
]
}
}
]
},
{
"id": 21,
"name": "Banana",
"description": "",
"sizeFromStorage": 98957268244,
"storagePools": [
{
"storagePool": {
"id": 2,
"name": "storage-pool1",
"sizeFromStorage": 98957268244,
"generations": [
{
"generation": {
"sequence": 0
}
}
]
}
}
]
},
]
}
}
The output I get from this is:
[[{vaults=[{id=20, name=Apple, description=, sizeFromStorage=95957225298, storagePools=[{storagePool={id=1, name=storage-pool1, sizeFromStorage=5043, estimateUsableTotalLogicalSizeFromStorage=95957225298, generations=[{generation={sequence=0}}]}}]}, {id=20, name=Apple, description=, sizeFromStorage=95957225298, storagePools=[{storagePool={id=1, name=storage-pool1, sizeFromStorage=5043, estimateUsableTotalLogicalSizeFromStorage=95957225298, generations=[{generation={sequence=0}}]}}]}]]
However, what I want to do is the name and sizeFromStorage values. So far I've managed to strip out the first three values. Unfortunately I'm now stuck as I'm not very familiar with Rest services or reading JSON. Is there any way I can delve further into the JSON to get what I need or have I approached this in the wrong way?
Additional info:
Since posting my original question I came up with this(based on something I saw on a different site):
package JsonRead;
import java.io.File;
import java.io.IOException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonTreeModel {
public static void main(String[] args) {
try{
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(new File("JsonRead/json.json"));
JsonNode vaultsNode = root.path("response").path("vault");
/*if(vaultsNode.isArray()){
for(JsonNode node : vaultsNode){
String name = node.path("name").asText();
System.out.println("Array Name: " + name);
}
}*/
for(JsonNode node : vaultsNode){
String name = node.path("name").asText();
String bytesUsed = node.path("sizeFromStorage").asText();
System.out.println("Name: " + name + ", Bytes Used: " + bytesUsed);
}
} catch(IOException e){
System.out.println("IO Exception " + e );
}
}
}
It works for what I want but is there a better way to do this?
Will, thanks for the response. I'll look into your suggestion.
Ok, so i just re-read your question.
Recommend creating yourself a VaultDAO object with a more meaningful custom object type to use for the response collections. Prvoided you use the same variable names (which it looks like are known to you) then it should deserialize for you

Dynamic Jackson Custom Deserializer

My JSON looks like this:
{"typeName":"test","field":{"name":"42"}}
I have two deserializers. The first one (JsonDeserializer<EntityImpl>) will examine the JSON and extract a type information which is provided by the typeName property.
The second deserializer (JsonDeserializer<TestField>) is used to deserialize the field property. This deserializer needs to know the previously extracted typeName value in order to work correctly.
How can i pass-along the type information from one deserializer to other deserializers? I tried to use DeserializationContext but i don't know how to pass along the Context from deserializer A to B.
My current code looks like this:
EntityImpl.java:
package de.jotschi.test;
public class EntityImpl implements Entity {
private String typeName;
private TestField field;
public String getTypeName() {
return typeName;
}
public void setTypeName(String typeName) {
this.typeName = typeName;
}
public TestField getField() {
return field;
}
public void setField(TestField field) {
this.field = field;
}
}
TestField.java:
package de.jotschi.test;
public class TestField {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Test:
package de.jotschi.test;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import de.jotschi.test.EntityImpl;
import de.jotschi.test.TestField;
public class TestMapper2 {
private InjectableValues getInjectableValue() {
InjectableValues values = new InjectableValues() {
#Override
public Object findInjectableValue(Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance) {
if ("data".equals(valueId.toString())) {
return new HashMap<String, String>();
}
return null;
}
};
return values;
}
#Test
public void testMapper() throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule idAsRefModule = new SimpleModule("ID-to-ref", new Version(1, 0, 0, null));
idAsRefModule.addDeserializer(EntityImpl.class, new JsonDeserializer<EntityImpl>() {
#Override
public EntityImpl deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null);
System.out.println("Value: " + dataMap.get("test"));
ObjectCodec codec = jp.getCodec();
JsonNode node = codec.readTree(jp);
String type = node.get("typeName").asText();
dataMap.put("typeName", type);
// How to pass on type information to TestField deserializer? The context is not reused for the next deserializer.
// I assume that readValueAs fails since the codec.readTree method has already been invoked.
//return jp.readValueAs(EntityImpl.class);
// Alternatively the treeToValue method can be invoked in combination with the node. Unfortunately all information about the DeserializationContext is lost. I assume new context will be created.
// How to reuse the old context?
return codec.treeToValue(node, EntityImpl.class);
}
});
idAsRefModule.addDeserializer(TestField.class, new JsonDeserializer<TestField>() {
#Override
public TestField deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// Access type from context
Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null);
System.out.println(dataMap.get("typeName"));
ObjectCodec codec = p.getCodec();
JsonNode node = codec.readTree(p);
return codec.treeToValue(node, TestField.class);
}
});
mapper.registerModule(idAsRefModule);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Setup the pojo
EntityImpl impl = new EntityImpl();
impl.setTypeName("test");
TestField testField = new TestField();
testField.setName("42");
impl.setField(testField);
// POJO -> JSON
String json = mapper.writeValueAsString(impl);
System.out.println(json);
// JSON -> POJO
Entity obj = mapper.reader(getInjectableValue()).forType(EntityImpl.class).readValue(json);
System.out.println(obj.getClass().getName());
}
}
My current solution is call the following mapper this way:
return mapper.setInjectableValues(getInjectableValue(dataMap)).treeToValue(obj, EntityImpl.class);
This way the previously loaded context data map is put into a new context that is used for the following parsing process.
Full example:
package de.jotschi.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class TestMapper2 {
private InjectableValues getInjectableValue(final Map<String, String> dataMap) {
InjectableValues values = new InjectableValues() {
#Override
public Object findInjectableValue(Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance) {
if ("data".equals(valueId.toString())) {
return dataMap;
}
return null;
}
};
return values;
}
#Test
public void testMapper() throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule idAsRefModule = new SimpleModule("ID-to-ref", new Version(1, 0, 0, null));
idAsRefModule.addDeserializer(Entity.class, new JsonDeserializer<Entity>() {
#Override
public Entity deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null);
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode obj = (ObjectNode) mapper.readTree(jp);
String type = obj.get("typeName").asText();
dataMap.put("typeName", type);
return mapper.setInjectableValues(getInjectableValue(dataMap)).treeToValue(obj, EntityImpl.class);
}
});
idAsRefModule.addDeserializer(TestField.class, new JsonDeserializer<TestField>() {
#Override
public TestField deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// Access type from context
Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null);
System.out.println("Type name: " + dataMap.get("typeName"));
ObjectMapper mapper = (ObjectMapper) p.getCodec();
ObjectNode obj = (ObjectNode) mapper.readTree(p);
// Custom deserialisation
TestField field = new TestField();
field.setName(obj.get("name").asText());
// Delegate further deserialisation to other mapper
field.setSubField(mapper.setInjectableValues(getInjectableValue(dataMap)).treeToValue(obj.get("subField"), SubField.class));
return field;
}
});
mapper.registerModule(idAsRefModule);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Setup the pojo
EntityImpl impl = new EntityImpl();
impl.setTypeName("test");
TestField testField = new TestField();
testField.setName("42");
SubField subField = new SubField();
subField.setName("sub");
testField.setSubField(subField);
impl.setField(testField);
// POJO -> JSON
String json = mapper.writeValueAsString(impl);
System.out.println(json);
// JSON -> POJO
Entity obj = mapper.reader(getInjectableValue(new HashMap<String, String>())).forType(Entity.class).readValue(json);
assertNotNull("The enity must not be null", obj);
assertNotNull(((EntityImpl) obj).getField());
assertEquals("42", ((EntityImpl) obj).getField().getName());
assertNotNull(((EntityImpl) obj).getField().getSubField());
assertEquals("sub", ((EntityImpl) obj).getField().getSubField().getName());
System.out.println(obj.getClass().getName());
}
}

Categories