Marshalling API response to List<POJO> with Jackson - java

I am having trouble parsing the response from an Adobe Campaign API endpoint into a POJO.
I am grabbing the data from the response:
String json = EntityUtils.toString(response.getEntity());
The data (heavily redacted) data looks like this:
{
"content": [
{
"PKey": "#9v59tLj9c.....",
"age": 36,
"birthDate": "1986-04-30",
"blackList": false,
...
},
{
"PKey": "#9f32tLj5c.....",
"age": 32,
"birthDate": "1999-05-11",
"blackList": false,
...
},
...
]
}
I'm instantiating a Jackson ObjectMapper and configuring it such that the root "content" node is ignored.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
I have tried many different ways of parsing the data into my Profile POJO, without success. There's always an issue related to it being wrapped in "content" node, or being a list of one, or something. For brevity, the code below is for a single POJO, but I have also tried with List<Profile> since, as mentioned, the response is always a List of one or more.
// object mapper
Profile profile = objectMapper.readValue(json), Profile.class)
// ERROR: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "content" (class com.example.adobecampaignprototype.Profile), not marked as ignorable (0 known properties: ])
// object reader
ObjectReader objectReader = objectMapper.readerFor(Profile.class).withRootName("content");
Profile profile = objectReader.readValue(json);
// ERROR: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `com.example.adobecampaignprototype.Profile` from Array value (token `JsonToken.START_ARRAY`)
// array node
ArrayNode arrayNode = (ArrayNode) objectMapper.readTree(json).get("content");
// ERROR: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "content" (class com.example.adobecampaignprototype.Profile), not marked as ignorable (0 known properties: ])
// json node
JsonNode jsonNodeRoot = objectMapper.readTree(json);
JsonNode jsonNodeNested = jsonNodeRoot.get("content");
JsonNode jsonNodeActual = jsonNodeNested.get(0) // to get profile at index 0
JsonNode jsonNodeActualValue = jsonNodeActual.get("PKey") // to read single property
I've tried the above in many combinations, but have never been able to successfully parse either a Profile or List. I have read the official docs exhaustively, been through tutorials on Baeldung and elsewhere. I feel like this should be a simple thing and there's probably something obvious that I'm overlooking, but unsure what it is. Would be grateful if someone could point me toward the EASY button.

Probably you are overthinking about it, for example if you take a semplified version of your json input file like below as a starting point:
{
"content": [
{
"PKey": "#9v59tLj9c....."
},
{
"PKey": "#9f32tLj5c....."
}
]
}
You can use part of the code you wrote, so one way to deserialize it is to convert your ArrayNode array of JsonNode into a Profile[] array:
#Data
public class Profile {
#JsonProperty("PKey")
private String pKey;
}
ArrayNode arrayNode = (ArrayNode) mapper.readTree(json).get("content");
Profile[] profiles = mapper.convertValue(arrayNode, Profile[].class);
//ok it prints the array [{"PKey":"#9v59tLj9c....."},{"PKey":"#9f32tLj5c....."}]
System.out.println(mapper.writeValueAsString(profiles));

Related

Stringify all values in a newline delimited JSON by keys

I have a newline delimited JSON and I wish to change values of type map to strings using Java. An example is:
{"id": 1, "data": {}}
{"id": 1, "data": {"time": 43}}
{"id": 1, "data": {"class" : {"students" : [{"name" : "Jane"}]}}}
should be changed to
{"id": 1, "data": "{}"}
{"id": 1, "data": "{\"time\": 43}"}
{"id": 1, "data": "{\"class\" : {\"students\" : [{\"name\" : \"Jane\"}]}}"}
I wish to stringify all the data map values. How can I do this?
This is a problem that calls for a JSON processor, since this will provide for the most bullet-proof, simple solutions. Java doesn't come with built-in JSON processing, so this will require a third party library. One commonly used Java JSON library is Jackson.
Since this particular request involves working with arbitrary data formats, the Jackson Tree Model in the Jackson Databind library is a good fit.
String delimitedJson = "{\"id\": 1, \"data\": {}}\n"
+ "{\"id\": 1, \"data\": {\"time\": 43}}\n"
+ "{\"id\": 1, \"data\": {\"class\" : {\"students\" : [{\"name\" : \"Jane\"}]}}}";
StringBuilder output = new StringBuilder();
JsonMapper jsonMapper = JsonMapper.builder().build();
MappingIterator<ObjectNode> i =
jsonMapper.readerFor(ObjectNode.class).readValues(delimitedJson);
i.forEachRemaining((ObjectNode jsonNode) ->
output.append(convertObjectsToString(jsonMapper, jsonNode).toString())
.append("\n"));
System.out.println(output);
// ...
private ObjectNode convertObjectsToString(JsonMapper jsonMapper,
ObjectNode jsonNode) {
ObjectNode copy = jsonMapper.createObjectNode();
jsonNode.fields().forEachRemaining(e -> {
if (e.getValue() instanceof ObjectNode) {
copy.set(e.getKey(), new TextNode(e.getValue().toString()));
} else {
copy.set(e.getKey(), e.getValue());
}
});
return copy;
}
Explanation
The ObjectMapper class is the primary class in the Jackson Databind library, and JsonMapper is the JSON-format specific implementation.
After creating the instance (using JsonMapper.builder().build()), we iterate over each element of the newline-delimited list using ObjectReader.readValues(delimitedJson), which reads a number of whitespace separated JSON values from an input source. In this example, I'm assuming the root level JSON values in your list are objects, and not e.g. strings or ints.
MappingIterator<ObjectNode> i =
jsonMapper.readerFor(ObjectNode.class).readValues(delimitedJson);
i.forEachRemaining((ObjectNode jsonNode) ->
output.append(convertObjectsToString(jsonMapper, jsonNode).toString())
.append("\n"));
The custom convertObjectsToString method returns an ObjectNode, which is converted to JSON using toString().
The convertObjectsToString method starts by creating a new empty ObjectNode. For each property in the source node, if it's not a JSON object type, it is copied over directly. If it is a JSON object, it is converted to the JSON string representation of the object.
private ObjectNode convertObjectsToString(JsonMapper jsonMapper,
ObjectNode jsonNode) {
ObjectNode copy = jsonMapper.createObjectNode();
jsonNode.fields().forEachRemaining(e -> {
if (e.getValue() instanceof ObjectNode) {
copy.set(e.getKey(), new TextNode(e.getValue().toString()));
} else {
copy.set(e.getKey(), e.getValue());
}
});
return copy;
}

How to replace json node in jsonNode or ObjectNode

I have a JSON node-like below. The structure of JsonNode will change dynamically.
Now I want to replace/update the value of a particular key.
Sample JSON One
{
"company": "xyz",
"employee": {
"name": "abc",
"address": {
"zipcode": "021566"
}
}
}
Sample JSON Two
{
"name": "abc",
"address": {
"zipcode": "021566"
}
}
I want to replace the zip code value 021566 to 566258. I know key name (zipcode), old and new zip code value but I don't know the path of zip code. I tried multiple ways using com.fasterxml.jackson.databind.JsonNode - put, replace
Is there any way to update in java?
JsonNodes are immutable, but you can find the value you want from a JsonNode, cast the original to an ObjectNode, replace the value, then cast that back to a JsonNode. It's a little odd, I know, but it worked for me.
public static void findAndReplaceJsonNode throws JsonProcessingException {
String jsonOne = "{ \"company\" : \"xyz\", \"address\" : { \"zipcode\" : \"021566\", \"state\" : \"FL\" } }";
String jsonTwo = "{ \"company\" : \"abc\", \"address\" : { \"zipcode\" : \"566258\", \"state\" : \"FL\" } }";
ObjectMapper mapper = new ObjectMapper();
JsonNode nodeOne = mapper.readTree(jsonOne);
JsonNode nodeTwo = mapper.readTree(jsonTwo);
JsonNode findNode = nodeTwo.get("address");
ObjectNode objNode = (ObjectNode) nodeOne;
objNode.replace("address", findNode);
nodeOne = (JsonNode) objNode;
System.out.println(nodeOne);
}
Output:
{"company":"xyz","address":{"zipcode":"566258","state":"FL"}}
Disclaimer: While I do some JSON parsing and processing on a regular basis, I certainly wouldn't say that I'm adept at it or tree traversals with them. There is more than likely a better way to find the value of a child in a JsonNode than by taking the entire child as a node, replacing it's one value and then replacing the node. This should work for you in a pinch, though :)

How to convert value with unicode in Json request into simple characters?

Sometime client send Json-RPC request with Json value as unicorde symboles.
Example:
{ "jsonrpc": "2.0", "method": "add", "params": { "fields": [ { "id": 1, "val": "\u0414\u0435\u043d\u0438\u0441" }, { "id": 2, "val": "\u041c\u043e\u044f" } ] }, "id": "564b0f7d-868a-4ff0-9703-17e4f768699d" }
How do I processing Json-RPC request:
My server get the request like byte[];
Convert it to io.vertx.core.json.JsonObject;
Make some manipulations;
Save to DB;
And I found in DB records like:
"val": "\u0414\u0435\u043d\u0438\u0441"
And the worst in this story. If client try to search this data, he'll get:
"val": "\\u0414\\u0435\\u043d\\u0438\\u0441"
So I think, that I need to convert request data before deserialization to JsonObject.
I tried and it didn't help:
String json = new String(incomingJsonBytes, StandardCharsets.UTF_8);
return json.getBytes(StandardCharsets.UTF_8);
Also I tried to use StandardCharsets.US_ASCII.
Note: Variant with StringEscapeUtils.unescapeJava() I can not, because it unescape all necessary and unnecessary '\' symbols.
If anyone know how to solve it? Or library that already makes it?
Thank a lot.
io.vertx.core.json.JsonObject depends on Jackson ObjectMapper to perform the actual JSON deserialization (e.g. io.vertx.core.json.Json has a ObjectMapper field). By default Jackson will convert \u0414\u0435\u043d\u0438\u0441 into Денис. You can verify this with a simple code snippet:
String json = "{ \"jsonrpc\": \"2.0\", \"method\": \"add\", \"params\": { \"fields\": [ { \"id\": 1, \"val\": \"\\u0414\\u0435\\u043d\\u0438\\u0441\" }, { \"id\": 2, \"val\": \"\\u041c\\u043e\\u044f\" } ] }, \"id\": \"564b0f7d-868a-4ff0-9703-17e4f768699d\" }";
ObjectMapper mapper = new ObjectMapper();
Map map = mapper.readValue(json, Map.class);
System.out.println(map); // {jsonrpc=2.0, method=add, params={fields=[{id=1, val=Денис}, {id=2, val=Моя}]}, id=564b0f7d-868a-4ff0-9703-17e4f768699d}
Most likely the client is sending something else because your example value is deserialized correctly. Perhaps it's doubly escaped \\u0414\\u0435\\u043d\\u0438\\u0441 value which Jackson will convert to \u0414\u0435\u043d\u0438\u0441 removing one layer of escaping?
There is no magic solution for this. Either write your own Jackson deserialization configuration or make the client stop sending garbage.

EOF Exception using Jackson

I am using Jackson to parse an external file which contains json. The json in the file takes this form:
{
"timestamp": MY_TIMESTAMP,
"serial": "MY_SERIAL",
"data": [{
MY_DATA
}, {
MY_DATA
}]
}
The code I am trying to use to access this is as follows:
JsonNode root = mapper.readTree(dataFileLocation);
JsonNode data = root.get("data");
ArrayList<AriaInactiveExchange> exchangeList = mapper.readValue(data.toString(), new TypeReference<List<AriaInactiveExchange>>(){});
I have validated the location of the dataFile and the data in it. I'm positive that i'm doing something wrong and that this may not even be the right approach. But the idea is clear that I need to get to "data" and map that to an Array.
When this code is run the following line instantly throws an EOF exception:
JsonNode root = mapper.readTree(dataFileLocation);

Issue with serializing single-element ArrayList to JSON

I am writing Junit test case for my REST service, i am setting the values(below is the piece of code) to get the payload required for REST service
Payload jsonPayload = new Payload ();
payload.setAcc("A");
List<Details> details= new ArrayList<Details>;
Details detail = new Details();
detail.setTotalAmount(1);
detail.setCurrency("dollar");
details.add(detail);
payload.getDetails().addAll(details);
I want JSON to be built in format mentioned below, but I am not getting the JSON as expected, details should be in form of Array.
Required JSON -
{
"Acc" : "A",
"details": [
{
"totalAmount":1,
"currency":"dollar"
}
]
}
Output JSON -
{
"Acc" : "A",
"details":
{
"totalAmount":1,
"currency":"dollar"
}
}
Can anyone help me how can I achieve this?

Categories