How can I convert String to Map using MapStruct? - java

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.

Related

Swap Jackson custom serializer / deserializer during runtime

I have the following system:
I am sending MediaType.APPLICATION_JSON_VALUEs from spring controllers to my client and vice versa.
I also have an export/import feature of my to-be-serialized classes. The JSON File is created by using an ObjectMapper and utilizing the writeValueAsString and readValue methods. I am reading from and writing into the json file.
Both of those serialization paths currently utilize the same serializers/deserializers.
I use the #JsonSerialize and #JsonDeserialize annotations to define custom serialization for some of my objects.
I want to serialize those objects differently for export/import.
So I want to swap the serializer / deserializer for the export/import task. Something like this:
If I understand the docs correctly, those two annotations only allow one using class. But I want to register multiple serializers/deserializers and use them based on some conditional logic.
You might want to have two separate ObjectMapper instances configured for Server and Client.
Server module:
ObjectMapper serverMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(ServerDTO.class, new CustomerFileSerializer());
module.addDeserializer(ServerDTO.class, new CustomerFileDeserializer());
serverMapper.registerModule(module);
ServerDTO serverDto = serverMapper.readValue(jsonInput, ServerDTO.class);
String serialized = serverMapper.writeValueAsString(serverDto);
and
Client module:
ObjectMapper clientMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(ClientDTO.class, new CustomerClientSerializer());
module.addDeserializer(ClientDTO.class, new CustomerClientDeserializer());
clientMapper.registerModule(module);
ClientDTO clientDTO = clientMapper.readValue(jsonInput, ClientDTO.class);
String serialized = clientMapper.writeValueAsString(clientDTO);
So I was trying to figure this out for the last few days. This is the progress I made so far:
I did two overrides for the default ObjectMapper in Spring and made sure they are configured like the default.
My custom mappers look like this:
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper defaultV7ObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
// emulate the default settings as described here: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-customize-the-jackson-objectmapper
objectMapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule module = new SimpleModule();
module.addSerializer(Customer.class, new CustomerClientSerializer());
module.addDeserializer(Customer.class, new CustomerClientDeserializer());
objectMapper.registerModule(module);
return objectMapper;
}
#Bean("exportImportMapper")
public ObjectMapper exportImportMapper() {
ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
// emulate the default settings as described here: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-customize-the-jackson-objectmapper
objectMapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule module = new SimpleModule();
module.addSerializer(Customer.class, new CustomerFileSerializer());
module.addDeserializer(Customer.class, new CustomerFileDeserializer());
objectMapper.registerModule(module);
return objectMapper;
}
}
I also removed the #JsonSerialize and #JsonDeserialize annotations from my entities.
HOWEVER there is one big difference with this change from annotations to adding the serializers via the module.
Let's say I have a class A that has a Customer property with the #JsonSerialize and #JsonDeserialize annotation.
Let's also say I have a class B that has a Customer property without annotations.
By removing the annotations and setting the serializer/deserializer as shown above I have now added theses serializers/deserializers to both Customer properties. So it's not equivalent.
Or am I missing something here?
This is my solution
It's not pretty but does its job.
I left my old jackson config untouched, so the client<->server serialization stays the same.
I then added this custom ObjectMapper to take care of my server<->file.
My custom ObjectMapper does the following things:
It registers a new custom JacksonAnnotationIntrospector, which I configured to ignore certain annotations. I also configured it to use my selfmade annotation #TransferJsonTypeInfo whenever a property has both the #TransferJsonTypeInfo as well as the #JsonTypeInfo annotation.
I registered my CustomerFileSerializer and CustomerFileDeserializer for this ObjectMapper.
#Service
public class ImportExportMapper {
protected final ObjectMapper customObjectMapper;
private static final JacksonAnnotationIntrospector IGNORE_JSON_ANNOTATIONS_AND_USE_TRANSFERJSONTYPEINFO = BuildImportExportJacksonAnnotationIntrospector();
public ImportExportMapper(){
customObjectMapper = new ObjectMapper().registerModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
// emulate the default settings as described here: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-customize-the-jackson-objectmapper
customObjectMapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
customObjectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule module = new SimpleModule();
module.addSerializer(Customer.class, new CustomerFileSerializer());
module.addDeserializer(Customer.class, new CustomerFileDeserializer());
customObjectMapper.setAnnotationIntrospector(IGNORE_JSON_ANNOTATIONS_AND_USE_TRANSFERJSONTYPEINFO);
customObjectMapper.registerModule(module);
}
public String writeValueAsString(Object data) {
try {
return customObjectMapper.writeValueAsString(data);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new IllegalArgumentException();
}
}
public ObjectTransferData readValue(String fileContent, Class clazz) throws JsonProcessingException {
return customObjectMapper.readValue(fileContent, clazz);
}
private static JacksonAnnotationIntrospector BuildImportExportJacksonAnnotationIntrospector() {
return new JacksonAnnotationIntrospector() {
#Override
protected <A extends Annotation> A _findAnnotation(final Annotated annotated, final Class<A> annoClass) {
if (annoClass == JsonTypeInfo.class && _hasAnnotation(annotated, FileJsonTypeInfo.class)) {
FileJsonTypeInfo fileJsonTypeInfo = _findAnnotation(annotated, TransferJsonTypeInfo.class);
if(fileJsonTypeInfo != null && fileJsonTypeInfo.jsonTypeInfo() != null) {
return (A) fileJsonTypeInfo.jsonTypeInfo(); // this cast should be safe because we have checked the annotation class
}
}
if (ignoreJsonAnnotations(annoClass)) return null;
return super._findAnnotation(annotated, annoClass);
}
};
}
private static <A extends Annotation> boolean ignoreJsonAnnotations(Class<A> annoClass) {
if (annoClass == JsonSerialize.class) {
return true;
}
if(annoClass == JsonDeserialize.class){
return true;
}
if(annoClass == JsonIdentityReference.class){
return true;
}
return annoClass == JsonIdentityInfo.class;
}
}
My custom annotation is defined and described like this:
/**
* This annotation inside of a annotation solution is a way to tell the importExportMapper how to serialize/deserialize
* objects that already have a wrongly defined #JsonTypeInfo annotation (wrongly defined for the importExportMapper).
*
* Idea is taken from here: https://stackoverflow.com/questions/58495480/how-to-properly-override-jacksonannotationintrospector-findannotation-to-replac
*/
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface FileJsonTypeInfo {
JsonTypeInfo jsonTypeInfo();
}
And it is used like this:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
#JsonTypeInfo(defaultImpl = Customer.class, property = "", use = JsonTypeInfo.Id.NONE)
#TransferJsonTypeInfo(jsonTypeInfo = #JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "customeridentifier"))
#JsonIdentityReference(alwaysAsId = true)
#JsonDeserialize(using = CustomerClientDeserializer.class)
#JsonSerialize(using = CustomerClientSerializer.class)
private Customer customer;

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)

Spring Boot using Json as request parameters instead of an entity/model

Our company is planning to switch our microservice technology to Spring Boot. As an initiative I did some advanced reading and noting down its potential impact and syntax equivalents. I also started porting the smallest service we had as a side project.
One issue that blocked my progress was trying to convert our Json request/response exchange to Spring Boot.
Here's an example of the code: (This is Nutz framework for those who don't recognize this)
#POST
#At // These two lines are equivalent to #PostMapping("/create")
#AdaptBy(type=JsonAdapter.class)
public Object create(#Param("param_1") String param1, #Param("param_2) int param2) {
MyModel1 myModel1 = new MyModel1(param1);
MyModel2 myModel2 = new MyModel2(param2);
myRepository1.create(myMode12);
myRepository2.create(myModel2);
return new MyJsonResponse();
}
On PostMan or any other REST client I simply pass POST:
{
"param_1" : "test",
"param_2" : 1
}
I got as far as doing this in Spring Boot:
#PostMapping("/create")
public Object create(#RequestParam("param_1") String param1, #RequestParam("param_2) int param2) {
MyModel1 myModel1 = new MyModel1(param1);
MyModel2 myModel2 = new MyModel2(param2);
myRepository1.create(myMode12);
myRepository2.create(myModel2);
return new MyJsonResponse();
}
I am not sure how to do something similar as JsonAdapter here. Spring doesn't recognize the data I passed.
I tried this but based on the examples it expects the Json paramters to be of an Entity's form.
#RequestMapping(path="/wallet", consumes="application/json", produces="application/json")
But I only got it to work if I do something like this:
public Object (#RequestBody MyModel1 model1) {}
My issue with this is that MyModel1 may not necessarily contain the fields/parameters that my json data has.
The very useful thing about Nutz is that if I removed JsonAdapter it behaves like a regular form request endpoint in spring.
I couldn't find an answer here in Stack or if possible I'm calling it differently than what existing spring devs call it.
Our bosses expect us (unrealistically) to implement these changes without forcing front-end developers to adjust to these changes. (Autonomy and all that jazz). If this is unavoidable what would be the sensible explanation for this?
In that case you can use Map class to read input json, like
#PostMapping("/create")
public Object create(#RequestBody Map<String, ?> input) {
sout(input.get("param1")) // cast to String, int, ..
}
I actually figured out a more straightforward solution.
Apparently this works:
#PostMapping("/endpoint")
public Object endpoint(#RequestBody MyWebRequestObject request) {
String value1 = request.getValue_1();
String value2 = request.getValue_2();
}
The json payload is this:
{
"value_1" : "hello",
"value_2" : "world"
}
This works if MyRequestObject is mapped like the json request object like so. Example:
public class MyWebRequestObject {
String value_1;
String value_2
}
Unmapped values are ignored. Spring is smart like that.
I know this is right back where I started but since we introduced a service layer for the rest control to interact with, it made sense to create our own request model object (DTOs) that is separate from the persistence model.
You can use #RequestBody Map as a parameter for #PostMapping, #PutMapping and #PatchMapping. For #GetMapping and #DeleteMapping, you can write a class which implements Converter to convert from json-formed request parameters to Map. And you would register that class as a bean with #Component annotation. Then you can bind your parameters to #RequestParameter Map.
Here is an example of Converter below.
#Component
public class StringToMapConverter implements Converter<String, Map<String, Object>> {
private final ObjectMapper objectMapper;
#Autowired
public StringToMapConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public Map<String, Object> convert(String source) {
try {
return objectMapper.readValue(source, new TypeReference<Map<String, Object>>(){});
} catch (IOException e) {
return new HashMap<>();
}
}
}
If you want to exclude specific field of your MyModel1 class, use #JsonIgnore annotation onto the field like below.
class MyModel1 {
private field1;
#JsonIgnore field2;
}
Then, I guess you can just use what you have done.(I'm not sure.)
public Object (#RequestBody MyModel1 model1) {}
i think that you can use a strategy that involve dto
https://auth0.com/blog/automatically-mapping-dto-to-entity-on-spring-boot-apis/
you send a json to your rest api that is map like a dto object, after you can map like an entity or use it for your needs
try this:
Add new annotation JsonParam and implement HandlerMethodArgumentResolver of this, Parse json to map and get data in HandlerMethodArgumentResolver
{
"aaabbcc": "aaa"
}
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonParam {
String value();
}
#Component
public class JsonParamMethodResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonParam.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
RepeatedlyRequestWrapper nativeRequest = webRequest.getNativeRequest(RepeatedlyRequestWrapper.class);
if (nativeRequest == null) {
return null;
}
Gson gson = new Gson();
Map<String, Object> response = gson.fromJson(nativeRequest.getReader(), new TypeToken<Map<String, Object>>() {
}.getType());
if (response == null) {
return null;
}
JsonParam parameterAnnotation = parameter.getParameterAnnotation(JsonParam.class);
String value = parameterAnnotation.value();
Class<?> parameterType = parameter.getParameterType();
return response.get(value);
}
}
#Configuration
public class JsonParamConfig extends WebMvcConfigurerAdapter {
#Autowired
JsonParamMethodResolver jsonParamMethodResolver;
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(jsonParamMethodResolver);
}
}
#PostMapping("/methodName")
public void methodName(#JsonParam("aaabbcc") String ddeeff) {
System.out.println(username);
}

Jackson filtering out fields without annotations

I was trying to filter out certain fields from serialization via SimpleBeanPropertyFilter using the following (simplified) code:
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("test",
SimpleBeanPropertyFilter.filterOutAllExcept("data1"));
try {
String json = mapper.writer(filterProvider).writeValueAsString(new Data());
System.out.println(json); // output: {"data1":"value1","data2":"value2"}
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
private static class Data {
public String data1 = "value1";
public String data2 = "value2";
}
Us I use SimpleBeanPropertyFilter.filterOutAllExcept("data1")); I was expecting that the created serialized Json string contains only {"data1":"value1"}, however I get {"data1":"value1","data2":"value2"}.
How to create a temporary writer that respects the specified filter (the ObjectMapper can not be re-configured in my case).
Note: Because of the usage scenario in my application I can only accept answers that do not use Jackson annotations.
If for some reason MixIns does not suit you. You can try this approach:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector(){
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
List<String> exclusions = Arrays.asList("field1", "field2");
return exclusions.contains(m.getName())|| super.hasIgnoreMarker(m);
}
});
You would normally annotate your Data class to have the filter applied:
#JsonFilter("test")
class Data {
You have specified that you can't use annotations on the class. You could use mix-ins to avoid annotating Data class.
#JsonFilter("test")
class DataMixIn {}
Mixins have to be specified on an ObjectMapper and you specify you don't want to reconfigure that. In such a case, you can always copy the ObjectMapper with its configuration and then modify the configuration of the copy. That will not affect the original ObjectMapper used elsewhere in your code. E.g.
ObjectMapper myMapper = mapper.copy();
myMapper.addMixIn(Data.class, DataMixIn.class);
And then write with the new ObjectMapper
String json = myMapper.writer(filterProvider).writeValueAsString(new Data());
System.out.println(json); // output: {"data1":"value1"}
The example of excluding properties by name:
public Class User {
private String name = "abc";
private Integer age = 1;
//getters
}
#JsonFilter("dynamicFilter")
public class DynamicMixIn {
}
User user = new User();
String[] propertiesToExclude = {"name"};
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Object.class, DynamicMixIn.class);
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("dynamicFilter", SimpleBeanPropertyFilter.filterOutAllExcept(propertiesToExclude));
mapper.setFilterProvider(filterProvider);
mapper.writeValueAsString(user); // {"name":"abc"}
You can instead of DynamicMixIn create MixInByPropName
#JsonIgnoreProperties(value = {"age"})
public class MixInByPropName {
}
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Object.class, MixInByPropName.class);
mapper.writeValueAsString(user); // {"name":"abc"}
Note: If you want exclude property only for User you can change parameter Object.class of method addMixIn to User.class
Excluding properties by type you can create MixInByType
#JsonIgnoreType
public class MixInByType {
}
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Integer.class, MixInByType.class);
mapper.writeValueAsString(user); // {"name":"abc"}
It seems you have to add an annotation which indicts which filter to use when doing the serialization to the bean class if you want the filter to work:
#JsonFilter("test")
public class Data {
public String data1 = "value1";
public String data2 = "value2";
}
EDIT
The OP has just added a note that just take the answer that not using a bean animation, then if the field you want to export is very less amount, you can just retrieve that data and build a Map of List yourself, there seems no other way to do that.
Map<String, Object> map = new HashMap<String, Object>();
map.put("data1", obj.getData1());
...
// do the serilization on the map object just created.
If you want to exclude specific field and kept the most field, maybe you could do that with reflect. Following is a method I have written to transfer a bean to a map you could change the code to meet your own needs:
protected Map<String, Object> transBean2Map(Object beanObj){
if(beanObj == null){
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(beanObj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
if (!key.equals("class")
&& !key.endsWith("Entity")
&& !key.endsWith("Entities")
&& !key.endsWith("LazyInitializer")
&& !key.equals("handler")) {
Method getter = property.getReadMethod();
if(key.endsWith("List")){
Annotation[] annotations = getter.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof javax.persistence.OneToMany){
if(((javax.persistence.OneToMany)annotation).fetch().equals(FetchType.EAGER)){
List entityList = (List) getter.invoke(beanObj);
List<Map<String, Object>> dataList = new ArrayList<>();
for(Object childEntity: entityList){
dataList.add(transBean2Map(childEntity));
}
map.put(key,dataList);
}
}
}
continue;
}
Object value = getter.invoke(beanObj);
map.put(key, value);
}
}
} catch (Exception e) {
Logger.getAnonymousLogger().log(Level.SEVERE,"transBean2Map Error " + e);
}
return map;
}
But I recommend you to use Google Gson as the JSON deserializer/serializer And the main reason is I hate dealing with exception stuff, it just messed up with the coding style.
And it's pretty easy to satisfy your need with taking advantage of the version control annotation on the bean class like this:
#Since(GifMiaoMacro.GSON_SENSITIVE) //mark the field as sensitive data and will not export to JSON
private boolean firstFrameStored; // won't export this field to JSON.
You can define the Macro whether to export or hide the field like this:
public static final double GSON_SENSITIVE = 2.0f;
public static final double GSON_INSENSITIVE = 1.0f;
By default, Gson will export all field that not annotated by #Since So you don't have to do anything if you do not care about the field and it just exports the field.
And if some field you are not want to export to json, ie sensitive info just add an annotation to the field. And generate json string with this:
private static Gson gsonInsensitive = new GsonBuilder()
.registerTypeAdapter(ObjectId.class,new ObjectIdSerializer()) // you can omit this line and the following line if you are not using mongodb
.registerTypeAdapter(ObjectId.class, new ObjectIdDeserializer()) //you can omit this
.setVersion(GifMiaoMacro.GSON_INSENSITIVE)
.disableHtmlEscaping()
.create();
public static String toInsensitiveJson(Object o){
return gsonInsensitive.toJson(o);
}
Then just use this:
String jsonStr = StringUtils.toInsensitiveJson(yourObj);
Since Gson is stateless, it's fine to use a static method to do your job, I have tried a lot of JSON serialize/deserialize framework with Java, but found Gson to be the sharp one both performance and handily.

MapStruct: mapping from java.util.Map to Bean?

I currently have a Map<String, String> that contains values in the form key = value and I would like to "expand" those into a real object.
Is it possible to automate that with MapStruct and how would I do that?
To clarify: The code I would write by hand would look something like this:
public MyEntity mapToEntity(final Map<String, String> parameters) {
final MyEntity result = new MyEntity();
result.setNote(parameters.get("note"));
result.setDate(convertStringToDate(parameters.get("date")));
result.setCustomer(mapIdToCustomer(parameters.get("customerId")));
// ...
return result;
}
Method 1
The MapStruct repo gives us useful examples such as Mapping from map.
Mapping a bean from a java.util.Map would look something like :
#Mapper(uses = MappingUtil.class )
public interface SourceTargetMapper {
SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );
#Mappings({
#Mapping(source = "map", target = "ip", qualifiedBy = Ip.class),
#Mapping(source = "map", target = "server", qualifiedBy = Server.class),
})
Target toTarget(Source s);
}
Notice the use of the MappingUtil class to help MapStruct figuring out how to correctly extract values from the Map :
public class MappingUtil {
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface Ip {
}
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public static #interface Server {
}
#Ip
public String ip(Map<String, Object> in) {
return (String) in.get("ip");
}
#Server
public String server(Map<String, Object> in) {
return (String) in.get("server");
}
}
Method 2
As per Raild comment on the issue related to this post, it is possible to use MapStruct expressions to achieve similar results in a shorter way :
#Mapping(expression = "java(parameters.get(\"name\"))", target = "name")
public MyEntity mapToEntity(final Map<String, String> parameters);
No note on performance though and type conversion may be trickier this way but for a simple string to string mapping, it does look cleaner.
Since version 1.5.0.Beta1 (Jul 2021) MapStruct supports mapping from Map to POJO.
Example:
#Mapper
public interface CustomerMapper {
#Mapping(target = "name", source = "customerName")
Customer toCustomer(Map<String, String> map);
}

Categories

Resources