Using Jackson to deserialize into a Map - java

I have a JSON object with two attributes: "key" which is a string, and "value" which can be deserialized into a Java bean.
{ "key": "foo", "value": "bar" }
The question is, given a list of such objects, can I deserialize it into a Map?
[{"key": "foo1", "value": "bar1"}, {"key": "foo2", "value": "bar2"}] -> Map<String, String>
Currently using Jackson-databind 2.1

You can easily convert above JSON to List<Map<String, String>>:
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
public class JacksonProgram {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
CollectionType mapCollectionType = mapper.getTypeFactory().constructCollectionType(List.class, Map.class);
List<Map<String, String>> result = mapper.readValue(json, mapCollectionType);
System.out.println(result);
}
}
Above program prints:
[{key=foo1, value=bar1}, {key=foo2, value=bar2}]

Since your structure does not match, you have two basic options:
Use two-phase handling: use Jackson to bind into intermediate representation that does match (which could be JsonNode or List<Map<String,Object>>), and then do conversion to desired type with simple code
Write custom serializer and/or deserializer
Jackson does not support extensive structural transformations (there are some simple ones, like #JsonUnwrapped), so this kind of functionality is unlikely to be added to databind module. Although it could be added as an extension module, if these "smart Map" types of structures are commonly used (they seem to be, unfortunately).

Had the same problem and was surprised Jackson wasn't able to natively handle it. Solution I went with was to create a custom setter on the object I was trying to marshal into :
public class somePojo {
private Map<String, String> mapStuff;
...
public void SetMapStuff(List<Map<String, String> fromJackson){
mapStuff= new HashMap<>();
for (Map<String, String> pair : fromJackson) {
put(pair.get("key"), pair.get("value"));
}
}
}
Jackson is smart enough to find that setter to and can happily pass it the List.

Related

Unable to model JSON with a random integer as KEY

I have a JSON response which looks like...
{
"profile": {
"userData": {
"338282892": [
{
"userIdentifier": "98shdub777hsjjsuj23",
"detail": "Test User DEV",
"type": "customer"
}
]
}
}
}
I have created a model, let's call it UserProfileModel.java. The model has properties using JSON to Java POJO converter, however when doing
UserProfileModel model = objectMapper.readValue(body, UserProfileModel.class);
I am getting below exception because the key user "338282892" because it can not be stored as variabale, for this case I tried to create map
Map<String, List<UserPropertiesModel>>
Here UserPropertiesModel is storing the userIdentifier, detail and type.
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "338282892"
I want to know if there is a way to deserialise this kind of JSON using object mapper such that I can do "object.getUserIdentifier()" or "object.getType()".
You can not model without having a valid "key" (which should be a string for mapping in a POJO class)
Alternatively, you can use a Map<String, Object> for traversing to the inner JSON blob.
Understand this,
Top layer is "profile" and anything as value to profile is value for map.
So you have a Map<"Profile" (a String), Object (whatever value profile has)> map1.
Then you do same for inner layer of "userData", so basically the object you stored in map1 is now again a Map<String, Object> map2 and same for deeper. layers.
This might not be the best approach but there is no way you can serialise this type of JSON using Mirconaut or Lombok or Spring.
Yes there is a way, with the use of a custom deserializer. Basically what you want to do is override the default behavior for deserializing a UserData object. Here's the deserializer definition:
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class UserDataDeserializer extends JsonDeserializer<UserData>{
#Override
public UserData deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
List<List<UserDataContent>> output = new ArrayList<>();
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = p.getCodec().readTree(p);
Iterator<JsonNode> iterator = jsonNode.elements();
while(iterator.hasNext()) {
JsonNode value = iterator.next();
System.out.println(value);
List<UserDataContent> obj = mapper.convertValue(value, new TypeReference<List<UserDataContent>>() {});
output.add(obj);
}
UserData returnVal = new UserData();
returnVal.setUserDataContent(output);
return returnVal;
}
}
And here's how you would use it:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(UserData.class, new UserDataDeserializer());
mapper.registerModule(module);
UserProfileModel model = mapper.readValue(body, UserProfileModel.class);
Here is a github repository with a complete working example.

mapstruct convert list to map

I am a very new to mapstruct. I am trying to convert List to Map, I've searched a lot online, I've got some solutions like its not yet implemented in mapstruct seems. I will be glad if someone could able to provide some alternative solution.
All I am looking to convert mapping as below:
#Mapping
Map<String, Object> toMap(List<MyObj>)
#Mapping
List<MyObj> toList(Map<String, Object>)
where MyObj as below:
class MyObj {
String key; //map key
String value; //map value
String field1;
}
In above, only use key and value fields from MyObj class. I've found one solution but below is converting some object to MAP, but using Jackson below:
#Mapper
public interface ModelMapper {
ObjectMapper OBJECT_MAPPER = new ObjectMapper();
default HashMap<String, Object> toMap(Object filter) {
TypeFactory typeFactory = OBJECT_MAPPER.getTypeFactory();
return OBJECT_MAPPER.convertValue(filter, typeFactory.constructMapType(Map.class, String.class, Object.class));
}
}
is there anyway now to implement using mapstruct?
Map struct doesn't have implicit conversion for your desired List to Map. You can have a custom mapping method as follows:
#Mapper
public interface FooMapper {
default Map<String, Foo> convertFooListToMap(List<Foo> foos) {
// custom logic using streams or however you like.
}
}
Other options include custom mapper implementations that you write and refer with something like #Mapper(uses=CustomMapper.class)

Java: Parsing JSON with unknown keys to Map

In Java, I need to consume JSON (example below), with a series of arbitrary keys, and produce Map<String, String>. I'd like to use a standard, long term supported JSON library for the parsing. My research, however, shows that these libraries are setup for deserialization to Java classes, where you know the fields in advance. I need just to build Maps.
It's actually one step more complicated than that, because the arbitrary keys aren't the top level of JSON; they only occur as a sub-object for prefs. The rest is known and can fit in a pre-defined class.
{
"al" : { "type": "admin", "prefs" : { "arbitrary_key_a":"arbitary_value_a", "arbitrary_key_b":"arbitary_value_b"}},
"bert" : {"type": "user", "prefs" : { "arbitrary_key_x":"arbitary_value_x", "arbitrary_key_y":"arbitary_value_y"}},
...
}
In Java, I want to be able to take that String, and do something like:
people.get("al").get("prefs"); // Returns Map<String, String>
How can I do this? I'd like to use a standard well-supported parser, avoid exceptions, and keep things simple.
UPDATE
#kumensa has pointed out that this is harder than it looks. Being able to do:
people.get("al").getPrefs(); // Returns Map<String, String>
people.get("al").getType(); // Returns String
is just as good.
That should parse the JSON to something like:
public class Person {
public String type;
public HashMap<String, String> prefs;
}
// JSON parsed to:
HashMap<String, Person>
Having your Person class and using Gson, you can simply do:
final Map<String, Person> result = new Gson().fromJson(json, new TypeToken<Map<String, Person>>() {}.getType());
Then, retrieving prefs is achieved with people.get("al").getPrefs();.
But be careful: your json string is not valid. It shouldn't start with "people:".
public static <T> Map<String, T> readMap(String json) {
if (StringUtils.isEmpty(json))
return Collections.emptyMap();
ObjectReader reader = new ObjectMapper().readerFor(Map.class);
MappingIterator<Map<String, T>> it = reader.readValues(json);
if (it.hasNextValue()) {
Map<String, T> res = it.next();
return res.isEmpty() ? Collections.emptyMap() : res;
}
return Collections.emptyMap();
}
All you need to do next, it that check the type of the Object. If it is Map, then you have an object. Otherwise, this is a simple value.
You can use Jackson lib to achieve this.
Put the following in pom.xml.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
Refer the following snippet that demonstrates the same.
ObjectMapper mapper = new ObjectMapper();
HashMap<String, Object> people = mapper.readValue(jsonString, new TypeReference<HashMap>(){});
Now, it is deserialized as a Map;
Full example:
import java.io.IOException;
import java.util.HashMap;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class testMain {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
String json = "{\"address\":\"3, 43, Cashier Layout, Tavarekere Main Road, 1st Stage, BTM Layout, Ambika Medical, 560029\",\"addressparts\":{\"apartment\":\"Cashier Layout\",\"area\":\"BTM Layout\",\"floor\":\"3\",\"house\":\"43\",\"landmark\":\"Ambika Medical\",\"pincode\":\"560029\",\"street\":\"Tavarekere Main Road\",\"subarea\":\"1st Stage\"}}";
ObjectMapper mapper = new ObjectMapper();
HashMap<String, Object> people = mapper.readValue(json, new TypeReference<HashMap>(){});
System.out.println(((HashMap<String, String>)people.get("addressparts")).get("apartment"));
}
}
Output: Cashier Layout

jackson deserialize string to a parameterized map

I have a json string in the following structure:
{
"1": { ... },
"2": { ... },
"3": { ... }
}
where 1, 2 and 3 are identifiers.
I want to deserialize this string and cast it to the following type - Map<Integer, List<MyCustomPojo>>
The thing is that each value { ... } has its own structure, but I need to parse them all and cast to a common structure - MyCustomPojo.
I can do it by implementing a custom deserializer:
public class CustomMapDeserializer extends JsonDeserializer<Map<Integer, List<MyCustomPojo>>> {
#Override
public Map<Integer, List<MyCustomPojo>> deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
JsonNode root = jsonParser.getCodec().readTree(jsonParser);
// parse JsonNode and return Map<Integer, List<MyCustomPojo>>
}
Now I need to add this deserializer to a module and then register it with an object mapper.
SimpleModule module = new SimpleModule();
module.addDeserializer(Map.class, new CustomMapDeserializer()); // can’t specify type for map
objectMapper.registerModule(module);
The problem is that now it will be applied to all Map classes in my application. I want it to apply only to parameterized map - Map<Integer, List<MyCustomPojo>>.
Is it possible to do it?
Simplest solution should be to create a new class which inherit from Map and use it when you add deserializer
The way you're thinking of isn't going to work, and might need to rewrite some of the code to apply ONLY to your parameterized map. I don't want to give any false info, sorry I can't provide a solution, but I hope this comment will help others to provide a solution for other abled coders.

How to save arbitrarily-structured documents with Java DynamoDBMapper class

I'm using Amazon's DynamoDBMapper Java class to save data to a DynamoDB table. This code needs to work for data structured in multiple different ways, so I would like to stay away from writing particularly structure-specific code. For this reason, I store the code as JSON objects in Java -- which are basically glorified HashMaps.
I would like to store these JSON objects into DynamoDB as Dynamo's relatively new JSON Document type.
The way the DynamoDBMapper API works is essentially that you write a Java class (typically a POJO), then add some annotations, then pass your objects of that class into DynamoDBMapper so that it can then put items into the database with the structure of the Java class. This works well for many aspects of what I'm doing, but not with the fact that I want these classes to contain arbitrarily-structured JSON documents. This is the way you're meant to store JSON documents using DynamoDBMapper, and as you can see, it doesn't allow for the structure of the documents to be arbitrary.
I realize I could use Dynamo's putItem() to pass the jsons as Strings into Item objects -- I just wanted to see if what I want to do is possible with DynamoDBMapper before I shift my approach.
You can try using the DynamoDB Java document SDK instead of the object mapper. This allows you to serialize and deserialize JSON strings using the fromJSON and toJSON methods in the Item class. Check out http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/JavaDocumentAPIItemCRUD.html.
Here's how I came up with my answer of how to store arbitrary Map objects in DynamoDB. This is extremely useful for archiving REST API responses that have been unmarshaled to foreign objects. I'm personally using this to archive REST responses from the PayPal Payment API. I don't care what variables they use in their REST API or the structure of their POJO / beans. I just want to make sure I save everything.
#DynamoDBTable(tableName = "PaymentResponse")
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes({
#JsonSubTypes.Type(value = PayPalPaymentResponse.class, name = "PayPalPaymentResponse"),
#JsonSubTypes.Type(value = BatchPayPalPaymentResponse.class, name = "BatchPayPalPaymentResponse")}
)
public abstract class PaymentResponse {
// store any arbitrary REST resrponse data in map form so we don't have to worry about the
// structure or the actual response itself
protected Map<String, String> paymentResponseData = Maps.newHashMap();
public PaymentResponse(PaymentResponseType paymentResponseType) {
this.paymentResponseType = paymentResponseType;
}
public Map<String, String> getPaymentResponseData() { return paymentResponseData; }
public void setPaymentResponseData(Map<String, String> paymentResponseData) { this.paymentResponseData = paymentResponseData; }
#Override
public String toString() {
return Arrays.toString(paymentResponseData.entrySet().toArray());
}
}
public class ConverterUtils {
public static BatchPayPalPaymentResponse getBatchPayPalPaymentResponse(PayoutBatch payoutBatch) throws IOException {
//read in the PayoutBatch response data and convert it first to a JSON string and then convert the
//JSON string into a Map<String, String>
Map<String, String> responseData = objectMapper.readValue(objectMapper.writeValueAsString(payoutBatch), new TypeReference<Map<String, String>>() {});
BatchPayPalPaymentResponse batchPayPalPaymentResponse = new BatchPayPalPaymentResponse(responseData);
return batchPayPalPaymentResponse;
}
public static PayPalPaymentResponse getSinglePayPalPaymentResponse(PayoutItemDetails payoutItemDetails) throws IOException {
//read in the paypal PayoutItemDetails response data and convert it first to a JSON string and then convert the
//JSON string into a Map<String, String>
Map<String, String> responseData = objectMapper.readValue(objectMapper.writeValueAsString(payoutItemDetails), new TypeReference<Map<String, String>>() {});
PayPalPaymentResponse payPalPaymentResponse = new PayPalPaymentResponse(responseData);
return payPalPaymentResponse;
}
}
public class BatchPayPalPaymentResponse extends PaymentResponse {
public BatchPayPalPaymentResponse(Map<String, String> responseData) {
super(responseData);
}
....
....
....
}
public class PayPalPaymentResponse extends PaymentResponse {
public PayPalPaymentResponse(Map<String, String> responseData) {
super(responseData);
}
....
....
....
}
Now you can just call mapper.save(instanceOfPaymentResponse). Note that my code also includes how to use a Jackson parser to pick and choose which sub-class of PaymentResponse to unmarshal too. That's because I use a DynamoDBTypeConverter to marshal my class to a string before putting it into the database.
Finally, I'll throw in my converter for completeness so it all hopefully makes sense.
public class PaymentResponseConverter implements DynamoDBTypeConverter<String, PaymentResponse> {
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
#Override
public String convert(PaymentResponse object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(String.format("Received invalid instance of PaymentResponse and cannot marshal it to a string (%s)", e.getMessage()));
}
}
#Override
public PaymentResponse unconvert(String object) {
try {
return objectMapper.readValue(object, PaymentResponse.class);
} catch (IOException e) {
throw new IllegalArgumentException(String.format("Unable to convert JSON to instance of PaymentResponse. This is a fatal error. (%s)", e.getMessage()));
}
}
}
I had the same problem and went the route of serializing and deserializing objects to json string by myself and then just store them as strings. The whole Document concept of DynamoDB is IMHO just a glorified object serializer. Only if you need to access attributes inside your object in dynamodb actions (eg. scans, projections) it makes sense to use the json document type. If our data is opaque to dynamodb, you are better off with strings.

Categories

Resources