Nested map of a pojo with Jackson - java

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'}

Related

Jackson exception: Could not resolve type id as a subtype of

I'm trying to parse following JSON string with one field inside. Unfortunatelly still getting exception:
InvalidTypeIdException: Could not resolve type id 'pin' as a subtype of `com.example.dto.AuthorizationRequest`: known type ids = [AuthorizationRequest]
Here is base class:
#JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT ,use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({
#JsonSubTypes.Type(value = AuthorizationRequest.class, name = "AuthorizationRequest")})
public interface IMessage {}
Derived class:
public class AuthorizationRequest implements IMessage {
#JsonProperty( value = "pin", required = true )
private String pin;
public String getPin() {
return pin;
}
public void setPin(String pin) {
this.pin = pin;
}
}
Test:
#Test
void test(){
String request =
"{\n"
+ " \"AuthorizationRequest\": {\n"
+ " \"pin\": \"1234\"\n"
+ " }\n"
+ "}";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
AuthorizationRequest authorizationRequest = null;
try {
authorizationRequest = objectMapper.readValue(request, AuthorizationRequest.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
What am I missing? I need to use request name as json root element.
You can delete objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true) line. The error is due to the fact that the line unwraps the initial object so escalating of one level at the intern of the json file obtaining the {"pin" : "1234"} json with the ""AuthorizationRequest" label. The JsonTypeInfo.As.WRAPPER_OBJECT annotation indicates to the jackson library to escalate again of one level the json file so obtaining the "1234" string labelled by "pin".The JsonTypeInfo.Id.NAME compares the "pin" string with the names of subclasses and because of it fails causing the issue and the error message.

Jackson double serialized json string to Java object

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");

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));

Java Json trouble reading hashmap

I'm trying to read this json file:
{
"username": "someusername",
"password": "12345678",
"ipAddresses": {
"2015-09-12": "127.0.0.1"
}
}
Using this class to store the info:
private final class SavedPlayer {
private final String username;
private final String password;
private final HashMap<LocalDate, String> ipAddresses;
private SavedPlayer(
String username, String password,
HashMap<LocalDate, String> ipAddresses
) {
this.username = username;
this.password = password;
this.ipAddresses = ipAddresses;
}
}
And this part of the code throws an exception:
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
SavedPlayer savedPlayer = GSON.fromJson(reader, SavedPlayer.class);
This is the thrown exception:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 5 column 17
How can I read this stored HashMap properly?
Edit: it works fine when i use <String, String> instead of <LocalDate, String>
Gson allows you to register your own custom serializers and deserializers. This is done by defining two parts:
Json Serialiers: Need to define custom serialization for an object
Json Deserializers: Needed to define custom deserialization for a type
Instance Creators: Not needed if no-args constructor is available or a deserializer is registered
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(LocalDate.class, new MyDeserializer());
registerTypeAdapter call checks if the type adapter implements more than one of these interfaces and register it for all of them.
for more information check gson-user-guide
Here is an example of how to write a custom deserialize for LocalDate
public class LocalDateJsonDeserializer implements JsonDeserializer<LocalDate> {
public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new LocalDate(json.getAsJsonPrimitive().getAsString());
}
}
and use this line for creating GSON.
final static Gson GSON = new GsonBuilder().registerTypeAdapter(LocalDate.class, new LocalDateJsonDeserializer()).setPrettyPrinting().create();
To bind JSON to LocalDate, you either have to write your custom serializer/deserialzer by implementing JsonDeserializer and registering with GSON using method registerTypeAdapter(), or you can use existing library for that: https://github.com/gkopff/gson-javatime-serialisers
Edit: You can find GSON deserializing key-value to custom object.
I tried the code you pasted with Gson 2.4 library. Also I converted the LocalDate to String. It is working absolutely fine.
public class SavedPlayer {
private final String username;
private final String password;
private final HashMap<String, String> ipAddresses;
private SavedPlayer(
String username, String password,
HashMap<String, String> ipAddresses
) {
this.username = username;
this.password = password;
this.ipAddresses = ipAddresses;
}
public static void main(String[] args) throws FileNotFoundException {
final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
SavedPlayer savedPlayer = GSON.fromJson("{\n" +
" \"username\": \"someusername\",\n" +
" \"password\": \"12345678\",\n" +
" \"ipAddresses\": {\n" +
" \"2015-09-12\": \"127.0.0.1\"\n" +
" }\n" +
"}", SavedPlayer.class);
System.out.println(savedPlayer.username);
System.out.println(savedPlayer.password);
for (Map.Entry entry : savedPlayer.ipAddresses.entrySet()) {
System.out.println("Key: " + entry.getKey() + "\tValue: " + entry.getValue());
}
}
}
Output:
someusername
12345678
Key: 2015-09-12 Value: 127.0.0.1

Parsing JSON with Jackson. Why HashMap instead of my type?

I'm using a Jackson library to parse JSON:
{
"employees": [
{ "firstName":"John" , "lastName":"Doe" },
{ "firstName":"Anna" , "lastName":"Smith" },
{ "firstName":"Peter" , "lastName":"Jones" }
]
}
Here is what I'm doing:
public void testJackson() throws IOException {
JsonFactory factory = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(factory);
File from = new File("emp.txt"); // JSON object comes from
TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {};
HashMap<String, Object> o = mapper.readValue(from, typeRef);
Employees employees = new Employees();
employees.employees = (List<Employer>)o.get("employees"); // retrieving list of Employer(s)
employees.showEmployer(1); // choose second to print out to console
System.out.println("Got " + o); // just result of file reading
}
public static class Employees {
public List<Employer> employees;
public void showEmployer(int i) {
System.out.println(employees.get(i));
}
}
public static class Employer {
public String firstName;
public String lastName;
}
The output I'm getting:
{firstName=Anna, lastName=Smith}
Got {employees=[{firstName=John,
lastName=Doe}, {firstName=Anna, lastName=Smith}, {firstName=Peter,
lastName=Jones}]}
But I'm not expecting the elements in my List to be HashMap instances, but Employer objects. This is what Jackson library is supposed to be, isn't it? Could you guys correct me where I am wrong?
I haven't used Jackson, but it seems you're getting what you asked for - a HashMap of String, Object pairs. Perhaps you need to be more explicit in the 'value' portion of the map? Since the value is an array of Employee objects, you might try:
TypeReference<HashMap<String, List<Employee>>> typeRef = new TypeReference<HashMap<String, List<Employee>>>() {};
HashMap<String, List<Employee>> o = mapper.readValue(from, typeRef);

Categories