Jackson double serialized json string to Java object - java

I have a Json string which has a string message field.
String:
{ "Type" : "Text",
"Subject" : "data received",
"Message" :"{\\"language\\":\\"US\\",\\"data\\":\\"signature\\"}"
}
I want to convert it into the following structure:
Notification.java
public class Notification {
String type;
String subject;
Message message;
}
Message.java
public class Message {
String language;
String data;
}
Is there a way in which I can directly convert the string to a Java object of the above structure? I want to avoid deserializing twice.

You can create a custom Deserializer to deserialize the Message text into Message object and annotate the Message class with #JsonDeserialize:
#JsonDeserialize(using = MessageDeserializer.class)
public class Message {
String language;
String data;
}
public class MessageDeserializer extends JsonDeserializer<Message> {
public MessageDeserializer() {
super();
}
#Override
public Message deserialize(
final JsonParser jsonParser, final DeserializationContext deserializationContext) throws
IOException, JsonProcessingException {
final String messageText = jsonParser.getText();
// parse messageText into Message object
}
}

I am not sure my solution is acceptable since it does require additional explicit call to ObjectMapper to perform deserialization of the string value of Message.
However, this is it is done during the buildup of Notification object and does not require a String message property.
You need to add a ctor with String argument to Message class, where you can deserialize the String into Map and extract the instance propertieds:
public Message(String str) {
try {
#SuppressWarnings("unchecked")
Map<String, Object> map =
(Map<String, Object>)new ObjectMapper().readValue(str, Map.class);
language = map.containsKey("language") ? map.get("language").toString() : null ;
data = map.containsKey("data") ? map.get("data").toString() : null ;
} catch (Exception e) {
e.printStackTrace();
}
}
the new ctor will be called by Jackson when you deserialize a Notification object:
Notification n = (Notification)new ObjectMapper().readValue(reader, Notification.class);

You can convert json string into key-value pairs in Map.You will have to do twice as the Message value is again a json string.Use org.json for JSONObject
Map<String, String> map = new HashMap<String, String>();
JSONObject j = new JSONObject(str);
Iterator<String> keys = j.keys();
while( keys.hasNext() ){
String key = (String)keys.next();
String val = j.getString(key);
map.put(key, val);}
Then retrieve the values by iterating over the keys and pass the values into the class constructor
Then map.get(key) can be used to retrieve the values and will be passed into constructors of the classes.

The org.json library is easy to use:
//Create Json object to parse string
// str is input string
JSONObject obj = new JSONObject(str);
//Create Message
Message mess = new Message();
JSONObject obj2 = new JSONObject(obj.getString("Message"));
mess.data = obj2.getString("data");
mess.language = obj2.getString("language");
//Create Notification
Notification noti = new Notification();
noti.message = mess;
noti.subject = obj.getString("Subject");
noti.type = obj.getString("Type");

Related

Nested map of a pojo with Jackson

I understand that I can create a map of a specified object with Jackson by using TypeReference. For instance, taking a class ...
public class Error {
/** Error code */
private final String errcode;
/** Error message */
private final String error;
// Getter
}
... and the data in JSON
{
"firstError":{
"errcode":"1234",
"error":"The 1. message"
},
"secondError":{
"errcode":"5678",
"error":"The 2. message"
}
}
... I can deserialize with
TypeReference<HashMap<String, Error>> typeRef = new TypeReference<HashMap<String, Error>>() {};
Map<String, Error> map = new ObjectMapper().readValue(jsonInput, typeRef);
My question is now: What can I do if my JSON looks like this?
{
"date":"2022-01-01",
"server":"myfancyserver",
"errors":{
"firstError":{
"errcode":"1234",
"error":"The 1. message"
},
"secondError":{
"errcode":"5678",
"error":"The 2. message"
}
}
}
First, in order to deserialize the nested Error objects containing final fields, it is needed to mark the all-args constructor with #JCreator annotation and use #JsonProperty to set the values properly:
public class Error {
private final String errcode;
private final String error;
#JsonCreator
public Error(#JsonProperty("errcode") String errcode, #JsonProperty("error") String error) {
this.errcode = errcode;
this.error = error;
}
// ... getters/toString, etc.
}
Then ObjectMapper::convertValue should be used to read the contents of the map from the JsonNode:
String json = ...; // input JSON string
ObjectMapper om = new ObjectMapper();
JsonNode node = om.readTree(json); // throws JsonProcessingException
Map<String, Error> errors = om.convertValue(
node.get("errors"), new TypeReference<Map<String, Error>>(){}
);
errors.forEach((k, v) -> System.out.println(k + " = " + v));
// -> firstError = {errcode='1234', error='The 1. message'}
// -> secondError = {errcode='5678', error='The 2. message'}

Spring Boot - JSON Deserialization

I am getting nested json object and json array as response while making ReST call. Nested json array and json object comes randomly. It may not be present as part of response everytime. I want to deserialize json in such a way that all the fields in json place at the root level of java object.
JSON Response
{
"data": {
"id":"42342sdz",
"details" : {
"username": "Username",
"location": "Location",
"history":[
{
"historyId":"34312cs", "historyDetail":"Some val", "datetime":"Some val",
"myObj":{
"myField" : "Some val"
}
},
{ "historyId":"34312cs", "historyDetail":"Some val", "datetime":"Some val"}
]
}
}
}
Java Object which I want to build by parsing above JSON response.
class ResponseObject {
String id;
String username;
String location;
List<History> historyList;
//Getters and Setters
}
class History {
String historyId;
String historyDetails
String datetime;
String myField;
//Getters and Setters
}
I'm not really sure what you mean when you say that the JSON object comes randomly. If you mean that the fields themselves are random (with random labels), then I'm not confident that you can store them as fields in a Java object like that. However, if you know the fields beforehand, then you can tell Jackson (the JSON deserializer that Spring Boot uses) how to deserialize the object by adding a method into your ResponseObject class that looks like this:
#SuppressWarnings("unchecked")
#JsonProperty("data")
private void unpackNested(Map<String, Object> data) {
this.id = (String) data.get("id");
Map<String, Object> details = (Map<String, Object>) data.get("details");
this.username = (String) details.get("username");
this.location = (String) details.get("location");
List<Map<String, Object>> originalHistory = (List<Map<String, Object>>) details.get("history");
historyList = new ArrayList<>();
if (originalHistory != null) {
for (Map<String, Object> h : originalHistory) {
History history = new History();
history.setHistoryId((String) h.get("historyId"));
history.setHistoryDetails((String) h.get("historyDetail"));
history.setDatetime((String) h.get("datetime"));
Map<String, Object> myObj = (Map<String, Object>) h.get("myObj");
if (myObj != null) {
history.setMyField((String) myObj.get("myField"));
}
historyList.add(history);
}
}
}
If you don't know the names of the fields, then I think the best you can do is store it into a flat Map<String, Object>.

Parse JSON response with numbers as Object

I have this JSON response, from a remote server and i really hope i can get help.
{
"data": {
"6111": {
"prereq": "0",
"mast": "The Master Tree"
},
"6112": {
"prereq": "1",
"mast": "Another Master Tree"
}
}
}
I use GSON to parse JSON, using the #SerializedName and #Exposeto obtain the value into a custom Model. But i do not understand how to get past the
"6111"
"6112"
I have checked other questions via the gson tag, to no avail.
try this
Iterator<String> iter = json.keys();
while (iter.hasNext()) {
String key = iter.next();
try {
Object value = json.get(key);
} catch (JSONException e) {
// Something went wrong!
}
}
///////////////////update////////////////////
JSONObject issueObj = new JSONObject(jsonContent);
Iterator iterator = issueObj.keys();
while(iterator.hasNext()){
String key = (String)iterator.next();
JSONObject issue = issueObj.getJSONObject(key);
// get id from issue
String _pubKey = issue.optString("id");
}
If you're using Gson, any time you have an object with keys you don't know ahead of time, you can just use Map instead of a custom object.
In this case, each element of the Map will be some "known" data structure, so you would use Map<String, MyObject>.
Your top-level class:
public class MyResponse {
#SerializedName("data")
#Expose
private Map<String, MyObject> data;
...
}
And your map's value class:
public class MyObject {
#SerializedName("prereq")
#Expose
private String prereq;
#SerializedName("mast")
#Expose
private String mast;
...
}
In the specific case of the json text you posted, you would then be able to use these objects like this:
response.getData().get("6111").getMast();
But you can also do anything you could normally do with a Map:
Map<String, MyObject> data = response.getData();
for (String key: data.keySet() {
...
}
for (MyObject obj : data.values()) {
...
}

Firebase Android: how do I access params nested in data via RemoteMessage?

via this shape:
{
"to": "000",
"priority": "high",
"data": {
"title": "A Title",
"message": "A Message",
"link": {
"url": "http://www.espn.com",
"text": "ESPN",
}
}
}
how can I access "url" and "text"?
String messageLink = remoteMessage.getData().get("link");
gets me:
{"text":"ESPN","url":"http://www.espn.com"}
but how do I drill deeper?
remoteMessage.getData().get("link").get("text");
doesnt quite work... I have also attempted JSONObject:
JSONObject json = new JSONObject(remoteMessage.getData());
JSONObject link = json.getJSONObject("link");
but this gives me try catch errors...
Any help and direction as always is greatly appreciated!
I would use gson and define a model class. The remote message gives you a Map<String, String> and their is no matching constructor for creating a json object.
Add gson to your build.xml:
compile 'com.google.code.gson:gson:2.5'
Create a notification model:
import com.google.gson.annotations.SerializedName;
public class Notification {
#SerializedName("title")
String title;
#SerializedName("message")
String message;
#SerializedName("link")
private Link link;
public String getTitle() {
return title;
}
public String getMessage() {
return message;
}
public Link getLink() {
return link;
}
public class Link {
#SerializedName("url")
String url;
#SerializedName("text")
String text;
public String getUrl() {
return url;
}
public String getText() {
return text;
}
}
}
Deserialize a notification object from the remote message.
If all your custom keys are at the top level:
Notification notification = gson.fromJson(gson.toJson(remoteMessage.getData()), Notification.class);
If your custom json data is nested in a single key for example "data" then use:
Notification notification = gson.fromJson(remoteMessage.getData().get("data"), Notification.class);
Note in this simple case the #SerializedName() annotations are unnecessary since the field names exactly match the keys in the json, but if you for example have a key name start_time but you want to name the java field startTime you would need the annotation.
As simple as that:
String linkData = remoteMessage.getData().get("link");
JSONObject linkObject = new JSONObject(linkData);
String url = linkObject.getString("url");
String text = linkObject.getString("text");
Of course, together with proper error handling.
Faced this issue when migrating from GCM to FCM.
The following is working for my use case, so perhaps it will work for you.
JsonObject jsonObject = new JsonObject(); // com.google.gson.JsonObject
JsonParser jsonParser = new JsonParser(); // com.google.gson.JsonParser
Map<String, String> map = remoteMessage.getData();
String val;
for (String key : map.keySet()) {
val = map.get(key);
try {
jsonObject.add(key, jsonParser.parse(val));
} catch (Exception e) {
jsonObject.addProperty(key, val);
}
}
// Now you can traverse jsonObject, or use to populate a custom object:
// MyObj o = new Gson().fromJson(jsonObject, MyObj.class)

How to get the pure Json string from DynamoDB stream new image?

I've a Dynamodb table with streaming enabled. Also I've created a trigger for this table which calls an AWS Lambda function. Within this lambda function, I'm trying read the new image (Dynamodb item after the modification) from the Dynamodb stream and trying to get the pure json string out of it. My Question is how can i get the pure json string of the DynamoDB item that's been sent over the stream? I'm using the code snippet given below to get the new Image, but I've no clue how to get the json string out of it. Appreciate your help.
public class LambdaFunctionHandler implements RequestHandler<DynamodbEvent, Object> {
#Override
public Object handleRequest(DynamodbEvent input, Context context) {
context.getLogger().log("Input: " + input);
for (DynamodbStreamRecord record : input.getRecords()){
context.getLogger().log(record.getEventID());
context.getLogger().log(record.getEventName());
context.getLogger().log(record.getDynamodb().toString());
Map<String,AttributeValue> currentRecord = record.getDynamodb().getNewImage();
//how to get the pure json string of the new image
//..............................................
}
return "Successfully processed " + input.getRecords().size() + " records.";
}
}
Below is the complete code for converting from Dynamo JSON to Standard JSON:
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.internal.InternalUtils;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.DynamodbEvent;
import com.amazonaws.services.lambda.runtime.events.DynamodbEvent.DynamodbStreamRecord;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Main Lambda class to receive event stream, parse it to Survey
* and process them.
*/
public class SurveyEventProcessor implements
RequestHandler<DynamodbEvent, String> {
private static final String INSERT = "INSERT";
private static final String MODIFY = "MODIFY";
public String handleRequest(DynamodbEvent ddbEvent, Context context) {
List<Item> listOfItem = new ArrayList<>();
List<Map<String, AttributeValue>> listOfMaps = null;
for (DynamodbStreamRecord record : ddbEvent.getRecords()) {
if (INSERT.equals(record.getEventName()) || MODIFY.equals(record.getEventName())) {
listOfMaps = new ArrayList<Map<String, AttributeValue>>();
listOfMaps.add(record.getDynamodb().getNewImage());
listOfItem = InternalUtils.toItemList(listOfMaps);
}
System.out.println(listOfItem);
try {
// String json = new ObjectMapper().writeValueAsString(listOfItem.get(0));
Gson gson = new Gson();
Item item = listOfItem.get(0);
String json = gson.toJson(item.asMap());
System.out.println("JSON is ");
System.out.println(json);
}catch (Exception e){
e.printStackTrace();
}
}
return "Successfully processed " + ddbEvent.getRecords().size() + " records.";
}
}
In c# you can convert newImage to pure json by use of DynamoDB Document class
using Amazon.DynamoDBv2.DocumentModel;
var streamRecord = dynamoEvent.Records.First();
var jsonResult=Document.FromAttributeMap(streamRecord.Dynamodb.NewImage).ToJson();
and if you want to go further ahead to convert json to object you can use Newtonsoft
using Newtonsoft.Json;
TModel model = JsonConvert.DeserializeObject(jsonResult);
Found a way of doing it cleanly. Using InternalUtils from aws-java-sdk-dynamodb-1.11.15.jar
com.amazonaws.services.dynamodbv2.model.Record streamRecord = ((RecordAdapter) record).getInternalObject();
// get order ready //
OrderFinal order = Utils.mapO2Object(
InternalUtils.toSimpleMapValue(streamRecord.getDynamodb().getNewImage().get("document").getM()),
OrderFinal.class );
Just summarizing the answer of Himanshu Parmar:
Map<String, AttributeValue> newImage = record.getDynamodb().getNewImage();
List<Map<String, AttributeValue>> listOfMaps = new ArrayList<Map<String, AttributeValue>>();
listOfMaps.add(newImage);
List<Item> itemList = ItemUtils.toItemList(listOfMaps);
for (Item item : itemList) {
String json = item.toJSON();
}
For those stuck with a Map<String, ?> where objects are plain Map, but not Attributes value, you can do the following:
Map<String, AttributeValue> dynamoDbAttributes =
objectMapper.convertValue(dynamoDbMap, new TypeReference<Map<String, AttributeValue>>() {});
and then convert this DynamoDB Map into a plain Map (equivalent to the json originally pushed into DynamoDb):
asMap = InternalUtils.toSimpleMapValue(dynamoDbAttributes);
For the ones facing issues with AttributeValue conversion refer the below code:
https://github.com/aws/aws-lambda-java-libs/blob/master/aws-lambda-java-events-sdk-transformer/README.md
Map<String, AttributeValue> stringAttributeValueMap = DynamodbAttributeValueTransformer.toAttributeValueMapV1(dynamodb.getNewImage());
List stringAttributeValueMapList = new ArrayList();
stringAttributeValueMapList.add(stringAttributeValueMap);
List<Item> listOfItem = InternalUtils.toItemList(stringAttributeValueMapList);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String updatedJSON = gson.toJson(listOfItem.get(0).asMap());
Below is the Method which converts DynamoDB JSON to normal JSON
/**
* Converts DynamoDB JSON to normal JSON.
*
* #param map Input map of String to AttributeValue.
* #return Returns an ObjectNode containing the normal JSON.
*/
public JsonObject toJsonObject(final Map<String, AttributeValue> map) {
final JsonNode result = mapToJsonObject(map);
final ObjectNode objectNode = (ObjectNode) result;
final ObjectMapper objectMapper = new ObjectMapper();
String recordObjectString;
try {
recordObjectString = objectMapper.writeValueAsString(objectNode);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
final JsonParser jsonParser = new JsonParser();
final JsonObject jsonObject = jsonParser.parse(recordObjectString)
.getAsJsonObject();
return jsonObject;
}
So in your Case simple call below method like this
// here record is of type DynamodbStreamRecord
toJsonObject(record.getDynamodb().getNewImage());
This library do the job: dynamoDb-marshaler
var unmarshalJson = require('dynamodb-marshaler').unmarshalJson;
console.log('jsonItem Record: %j', unmarshalJson(record.dynamodb.NewImage));

Categories