Spring and Gson - Expose fields only on certain conditions - java

I'm using Spring with Gson to object serialization.
I have model objects that use #Expose annotation e.g.:
public class Zone {
#Expose
private String name;
#Expose
private String description;
#Expose
private List<String> longList;
private String someIrrelevantVar;
}
I'm have 2 controllers which serves Zone objects list to user e.g.:
#RestController
class ZoneController {
#GetMapping(value = "fullData")
List<Zone> getFullZones() {
return zoneService.getZones();
}
}
#RestController
class SimpleZoneController {
#GetMapping(value = "simpleData")
List<Zone> getSimpleZones() {
return zoneService.getZones();
}
}
The problem is List<String> longList var - it usually has a lot of entries (String is only example, in code it could be complex object).
In my getFullZones() I want to serve to user zones with this longList but in getSimpleZones() I want ot serve zones without longList (it's not used in any way on the client side).
How to do that?
I could iterate list with zones and set longList to null but it's not very elegant solution.
I'm setting up Spring to use Gson like this:
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(createGsonHttpMessageConverter());
super.configureMessageConverters(converters);
}
private GsonHttpMessageConverter createGsonHttpMessageConverter() {
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
//.registerTypeAdapter - register some deserializers
.create();
GsonHttpMessageConverter gsonConverter = new GsonHttpMessageConverter();
gsonConverter.setGson(gson);
return gsonConverter;
}
}

Create a base class ZoneSimple and extend another class Zone extends ZoneSimple. Move the #Expose from fields to methods.
In the base class the method has no annotation. In the Zone the method is annotated.
Alternatively you can add a ProxyZone class which keeps zone instance and delegates all the calls to the instance. Annotate the Proxy as you need.
Another way is
Gson gson = new GsonBuilder()
.addSerializationExclusionStrategy(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getName().toLowerCase().contains("fieldName");
}
#Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
})
.create();
Got from the answer

Related

ModelMapper map List<...> to object property

I am struggling with mapping List into responseDTO.getList()
My code:
MessageDTO
#Getter
#Setter
public Class MessageDTO() {
private String message;
...
}
MessagesDTO
#Getter
#Setter
public Class MessagesDTO() {
private List<> message;
}
MyConverter
public class MyConverter extends AbstractConverter<List<MessageDTO>, MessagesDTO> {
#Override
protected ChatMessagesResponseDTO convert(List<MessageDTO> source) {
MessagesDTO destination = new MessagesDTO();
destination.setMessages(source);
return destination;
}
}
Controller
...
List<MessageDTO> messages = ... // result of service and succesfull mapping entity to dto
ModelMapper mm = new ModelMapper();
Converter conv = new MyConverter();
mm.addConverter(conv);
MessagesDTO messagesDTO = mm.map(messages, MessagesDTO.class)
return messagesDTO; // always null
Any ideas why it is not working ? I am sucessfuly using modelmapper in many other places of my project even with custom TypeMap(s) and Converter(s), but cannot find a way how to map list of some type into DTO attribute which is list of that type.
This is because of type erasure. ModelMapper is unable to recognize the generic type of a List and thus does not apply your converter. I'm not sure if it is possible to achieve with classes you presented but if it is it might be quite complicated task.
One solution would be to declare class that has the type stored runtime. So like:
#SuppressWarnings("serial")
public static class MessageDTOList extends ArrayList<MessageDTO> {};
and make required changes to your converter, so to be:
public class MyConverter extends AbstractConverter<MessageDTOList, MessagesDTO> {
#Override
protected MessagesDTO convert(MessageDTOList source) {
MessagesDTO destination = new MessagesDTO();
destination.setMessages(source);
return destination;
}
}
If it is hard to get the response directly as a MessageDTOList you can always:
List<MessageDTO> messages = ... // result of service and succesfull mapping entity
MessageDTOList messagesDerived = new MessageDTOList();
messagesDerived.addAll(messages);
and then just:
MessagesDTO messagesDTO = mm.map(messagesDerived, MessagesDTO.class);

How to inject guice dependency in mapstruct interface - java

My objects
Public void Student(){
private string name;
private int age;
}
Public void ClassRoom(){
private string roomNo;
private Student student; //Student Object
}
Public void School(){
private string roomNo;
private String student; //String student
}
I have an interface
#Mapper(componentModel = "jsr330", unmappedTargetPolicy = ReportingPolicy.IGNORE, builder = #Builder(disableBuilder = true))
public interface TestjkMapper {
#Named("convertObjToString")
static String convertObjToString(Student student) {
return new Gson.tojson(student)
}
#Mapping(source = "student", target = "student",qualifiedByName = "convertObjToString")
School mapClassRoomToSchool(#NonNull ClassRoom classRoom);
}
I am planning to inject that gson instead of new Gson()
As per this How can i combine Guice and Mapstruct?
I tried to make changes but not sure where to add that
#Inject
Gson gson
I tried like this
public interface TestjkMapper {
#Inject
Gson gson
#Named("authorityToMap")
static Map authorityToMap(Authority authority) {
return gson.tojson(authority.tostring())
}
}
I am getting compiler error as below
variable gson might not have been initialized
how to inject correctly inside that interface
Here also
bind(TestjkMapper.class).to(TestjkMapperImpl.class)
TestjkMapper - My interface
TestjkMapperImpl - This is generated at runtime. So not sure how to bind this
Exact question:
How to convert Student object to Student String using guice injected gson
If you want to use custom injected components in your mappers you will need to use abstract classes instead of interfaces. You can't inject fields into interfaces.
In your example it will look like:
#Mapper(componentModel = "jsr330", unmappedTargetPolicy = ReportingPolicy.IGNORE, builder = #Builder(disableBuilder = true))
public abstract class TestjkMapper {
#Inject
Gson gson;
#Named("convertObjToString")
static String convertObjToString(Student student) {
return gson.tojson(student)
}
#Mapping(source = "student", target = "student", qualifiedByName = "convertObjToString")
School mapClassRoomToSchool(#NonNull ClassRoom classRoom);
}

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

Object mapping with org.modelmapper.modelmapper

I have 2 objects :
#Setter
#Getter
public class Agent {
public int userID;
public String name;
public boolean isVoiceRecorded;
public boolean isScreenRecorded;
public boolean isOnCall;
public LocalDateTime startEventDateTime;
}
public class AgentLine {
public int userID;
public String name;
public boolean isVoiceRecorded;
public boolean isScreenRecorded;
public boolean isOnCall;
public String startEventDateTime;
}
I would like to map between AgentLine to Agent. I can't use the default mapping because of the Localdatetime conversion.
I have defined :
#Bean
ModelMapper getModelMapper() {
ModelMapper modelMapper = new ModelMapper();
Converter<AgentLine, Agent> orderConverter = new Converter<AgentLine, Agent>() {
#Override
public Agent convert(MappingContext<AgentLine, Agent> mappingContext) {
AgentLine s = mappingContext.getSource();
Agent d = mappingContext.getDestination();
/* d.userID = s.userID;
d.name = s.name;*/
d.startEventDateTime = LocalDateTime.parse(s.startEventDateTime, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
return d;
}
};
modelMapper.addConverter(orderConverter);
return modelMapper;
}
In order to use it:
AgentLine line;
#Autowired
private ModelMapper modelMapper;
Agent agent = modelMapper.map(line, Agent.class);
It works , but I do not want to specify all Agent properties in the convert method, I would like to specify the startEventDateTime conversion and the rest of the properties would be mapped by default.
In addition I have tried to define :
PropertyMap<AgentLine, Agent> orderMap = new PropertyMap<AgentLine, Agent>() {
#Override
protected void configure() {
map().setName(source.name);
}
};
modelMapper.addMappings(orderMap);
but , in the mapping you can't handle the date conversion.
If I define for the mapper PropertyMap and Converter the PropertyMap is ignored.
I do not want to specify all Agent properties in the convert method, I would like to specify the startEventDateTime conversion and the rest of the properties would be mapped by default.
Do not use Converter for mapping complex objects. You should use TypeMap for such purposes. Use Converter for custom conversion (for your case String to LocalDateTime).
ModelMapper modelMapper = new ModelMapper();
Converter<String, LocalDateTime> dateTimeConverter = ctx -> ctx.getSource() == null ? null : LocalDateTime.parse(ctx.getSource(), DateTimeFormatter.ISO_LOCAL_DATE_TIME);
modelMapper.typeMap(AgentLine.class, Agent.class)
.addMappings(mapper -> mapper.using(dateTimeConverter).map(AgentLine::getStartEventDateTime, Agent::setStartEventDateTime));

JSON to Java object deserialization with escaped properties

I need to convert the following JSON to Java object. The property providerResponse in the JSON contains map of properties but they are escaped and wrapped in doubleQuotes. As a result, it does not deserialize the property providerResponse into a Java object (it comes as String). I use objectMapper.readValue(msgStr, classType) to deserialize the JSON. The message is generated by AWS for SNS delivery status notifications and I don't have control to change the JSON message. Is it possible to configure ObjectMapper to unescape the property and deserialize into a Java object instead of String?
{
"delivery":{
"providerResponse":"{\"sqsRequestId\":\"308ee0c6-7d51-57b0-a472-af8e6c41be0b\",\"sqsMessageId\":\"88dd59eb-c34d-4e4d-bb27-7e0d226daa2a\"}"
}
}
#JsonProperty("providerResponse")
private String providerResponse;
There doesn't seem to be a way to configure ObjectMapper to handle this behavior by default. The solution is to create a custom JsonDeserializer:
public class Wrapper {
public Delivery delivery;
}
public class Delivery {
#JsonDeserialize(using = ProviderResponseDeserializer.class)
public ProviderResponse providerResponse;
}
public class ProviderResponse {
public String sqsRequestId;
public String sqsMessageId;
}
public class ProviderResponseDeserializer extends JsonDeserializer<ProviderResponse> {
private static final ObjectMapper mapper = new ObjectMapper();
#Override
public ProviderResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return mapper.readValue(jsonParser.getText(), ProviderResponse.class);
}
}
Then you can deserialize the JSON by using your ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(JSON, Wrapper.class);
I faced this similar issue. This gets resolved if we define a constructor in ProviderResponse which takes a single string argument (which is actually json) and then map the json in the constructor to the instance of ProviderResponse and use this temp instance to initialise the properties.
public class Wrapper {
public Delivery delivery;
}
public class Delivery {
public ProviderResponse providerResponse;
}
public class ProviderResponse {
public String sqsRequestId;
public String sqsMessageId;
private static ObjectMapper objMapper = new ObjectMapper();
public ProviderResponse(String json) {
ProviderResponse temp = objMapper.readValue(json, ProviderResponse.class);
this.sqsMessageId = temp.sqsMessageId;
this.sqsRequestId = temp.sqsRequestId;
}
}
The key is to keep the ObjectMapper instance and the its usage somewhere in your utility class and use it from there.

Categories

Resources