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)
Related
I'm refactoring a camel route to hopefully be a little more generic. (I'm also using spring boot, if that helps for any possible bean injection solutions)
from(fromKafka)
.routeId("Rest Models")
.removeHeaders("*")
.aggregate(new GroupedBodyAggregationStrategy())
.constant(true)
.completionTimeout(batchingInterval)
.process(new ListOfJsonToJsonArray())
.unmarshal().json(JsonLibrary.Jackson, InputArrayPojo.class)
.enrich("seda:rest", mergeRestResult)
the processor ListOfJsonToJsonArray() takes the json string representation of the kafka message, and joins everything, comma separated, with a {[ ]} on the outside.
The InputArrayPojo.class is thus a wrapper for the array of objects that are coming in from kafka. I need to bundle the objects in order to mini-batch to the REST interface in the enrichment. The objects contained are of format InputPojo.class (effectively just a schema, but also performs some basic data quality checks)
I need a way to generify InputPojo.class such that for our new jobs, we can run the same route, but supply a different InputPojo.class.
I've tried to apply polymorphism and create an interface for InputPojo, however this runs into an error when trying to construct the interface.
#JsonSubTypes({
#JsonSubTypes.Type(value=InputPojo.class, name = "online")
})
public interface InputPojoInterface {
}
I also tried some parameterisation, but I had no luck there either because it would not apply the constructor of the bean, none of the methods then existed.
I've also included
com.fasterxml.jackson.databind.exc.InvalidDefinitionException - Cannot construct instance of `InputPojoInterface` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (ByteArrayInputStream); line: 1, column: 10] (through reference chain: InputArrayPojo["data"]->java.util.ArrayList[0])]
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"data"
})
public class InputArrayPojo{
#JsonProperty("data")
private List<InputPojo> data = null;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("data")
public List<InputPojo> getData() {
return data;
}
#JsonProperty("data")
public void setData(List<InputPojo> data) {
this.data = data;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
The enrichment also needs to implement some type of generifying logic
#Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
List<IngestionOutPojo> originalMessages = oldExchange.getIn().getBody(IngestionOutArrayPojo.class).getData();
List<PredictionPojo> enrichmentMessages = newExchange.getIn().getBody(PredictionArrayPojo.class).getData();
List<ModelResultPojo> outputList = new ArrayList<>();
for (int i = 0; i < originalMessages.size(); ++i) {
ModelResultPojo output = new ModelResultPojo();
IngestionOutPojo raw = originalMessages.get(i);
PredictionPojo enrich = enrichmentMessages.get(i);
/*
enrichment logic to create modelResult
*/
outputList.add(modelResult)
}
newExchange.getIn().setBody(outputList);
return newExchange
}
I ended up coming up with a solution by doing the following:
unmarshalled to the default type: Map<String,Object> (without specifying a class, it camel unmarshalls to a Map<String,Object>)
After that I wrote an abstract class that implements a processor. In this processor I take the Map, and apply an abstract editFields() function to the Map.
thus I now have polymorphic handling of business logic through a Map instead of through a POJO.
I have several YAML config files I want to deserialize into a class. The YAML in the files consists of simple name value pairs with no nesting. There's a handful of properties that will have explicit fields, but the rest I just want dumped into a Map.
This all works fine, but I also want all the values of the properties that get deserialized into the Map through .add() to be run through a custom deserializer. I've tried using #JsonDeserialize on the setter value parameter and the setter method itself but Jackson seems to ignore it altogether.
Here's how it's set up:
public class ConfigData {
private Map<String, Object> dynamicConfig = new LinkedHashMap<>();
#JsonAnyGetter
public Map<String, Object> getConfig() {
return dynamicConfig;
}
#JsonAnySetter
public void add(String name, #JsonDeserialize(using = FooDeserializer.class) Object value) {
dynamicConfig.put(name, value);
}
#JsonProperty("some_special_property")
public String setSomeSpecialProperty(String value) {
add("some_special_property", value);
}
}
And to deserialize:
public static ConfigData getConfig(URL configResource) throws IOException {
try (InputStream stream = configResource.openStream()) {
ObjectMapper mapper = new YAMLMapper();
return mapper.readValue(new InputStreamReader(stream, StandardCharsets.UTF_8), ConfigData.class);
}
}
I discovered the problem was that I was specifying the deserializer class with the using property of the #JsonDeserialize annotation. For this specific use case I needed to use the contentUsing property instead, which is used for things like the value field of a Map entry.
This is what my setter looks like now:
#JsonAnySetter
#JsonDeserialize(contentUsing = FooDeserializer.class)
public void add(String name, Object value) {
dynamicConfig.put(name, value);
}
Now all the values will be serialized using FooDeserializer, except for "some_special_property" which has its own setter.
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
I have Story entity in my Spring Boot application. It has String field storyInfo which contains:
{"title":"random title", "description":"random description"}
For my Story entity I have StoryDTO with map field called storyInfo.
The question is: how can I convert String field from Strory into Map in StoryDTO using MapStruct?
Try following code, inspired from here
#Mapper(componentModel = "spring")
public interface StoryMapper {
#Mappings({
#Mapping(source = "storyInfo", target = "storyInfo", qualifiedByName = "fromJsonToMap")
})
StoryDTO toStoryDTO(Story story);
#Mappings({
#Mapping(source = "storyInfo", target = "storyInfo", qualifiedByName = "fromMapToJson")
})
Story toStory(StoryDTO storyDTO);
#Named("fromJsonToMap")
default Map<String, Object> fromJsonToMap(String storyInfo) throws IOException {
if (Objects.nonNull(storyInfo)) {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Double> result = objectMapper.readValue(storyInfo, new TypeReference<Map<String, Object>>() {});
return result;
}
return null;
}
#Named("fromMapToJson")
default String fromMapToJson(Map<String, Object> storyInfo) throws JsonProcessingException {
if (Objects.nonNull(storyInfo)) {
ObjectMapper objectMapper = new ObjectMapper();
String result = objectMapper.writeValueAsString(storyInfo);
return result;
}
return null;
}
}
Thank you guys for answers. Found the easiest solution for me by adding few manual mappers to MapStruct's StoryMapper interface.
// Manual convert to Map
default Map toMap(String text){
Map map = new HashMap();
try {
map = new ObjectMapper().readValue(text, new TypeReference<Map<String, String>>(){});
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
// Manual convery from map
default String fromMap(Map map){
return new JSONObject(map).toString();
}
The already provided answer explains well how you can provide a Service to do the mapping with Jackson.
In order to make this work with MapStruct you can use qualifiers and annotate your service accordingly.
For example
#Qualifier // from the MapStruct package
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.CLASS)
public #interface FromJson {
}
public interface StringToMapConverter {
#FromJson
Map<String, String> convert(String string);
}
#Mapper(componentModel = "spring")
public interface MyMapper {
#Mapping(target = "storyInfo", qualifiedBy = FromJson.class)
StoryDTO convert(Story story);
}
The implementation of StringToMapConverter should be as in the already provided answer. You don't have to use a dedicated interface for the converter, you an also use an abstract mapper, inject the ObjectMapper and do the rest same.
MapStruct will then use it to convert the storyInfo String into the map.
Some other possible solution, outside of the scope of the question and if you use Hibernate. You can use Map<String, String> in your entity, but still map it to String in the DB. Have a look at hibernate-types by Vlad Mihalcea, it allows using extra types so you can persist objects as JSON in the database
You can create a generic tool class so other Mapper can also use.
p.s: JsonUtil just a util clss use to transform Object to Json.
And you can use jackson, fastjson, gson.
#Component
public class MapStructUtil {
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface JsonStrToMap {
}
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface MapToJsonStr {
}
#JsonStrToMap
public Map jsonStrToMap(String jsonStr) {
return JsonUtil.toMap(jsonStr);
}
#MapToJsonStr
public String mapToJsonStr(Map<String, String> map) {
return JsonUtil.toJsonString(map);
}
}
Then you can use it in your Mapper like this
p.s: Here use componentModel = "spring", so you need to add #Componnent annotation in MapStructUtil
#Mapper(componentModel = "spring", uses = {MapStructUtil.class})
public interface StoryMapper {
#Mapping(source = "storyInfo", target = "storyInfo", qualifiedBy = JsonStrToMap.class)
StoryDTO toStoryDTO(Story story);
#Mapping(source = "storyInfo", target = "storyInfo", qualifiedBy = MapToJsonStr.class)
Story toStory(StoryDTO storyDTO);
}
I am not familiar with MapStruct, but I might suggest an alternative since you are running your application in a Spring context.
Since your string is a JSON string, your best course of action would be to use a JSON library. Spring Boot comes with its own preconfigued instance of the Jackson ObjectMapper (which you may override to add/remove specific features by defining using a #Bean of type ObjectMapper in any #Configuration class).
You might inject an instance of this using:
#Autowired
ObjectMapper objectMapper;
After that, you are able to use the object mapper to transform the string into a HashMap<String, String> (or whichever types you need) as follows:
Map<String, String> result = objectMapper.readValue(storyInfo, new TypeReference<Map<String, String>>() {});
I will try to update this answer with a MapStruct approach, but perhaps this might be more practical for you at this time.
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.