Dynamically replacing values in JSON response with Spring Boot and Thymeleaf - spring-boot

Using Spring Boot I'd like to implement a mock service for an external API. As this mock is only used for testing, I'd like to keep things as simple as possible. The external API returns a JSON similar to this one:
{
"customer": {
"email": "foo#bar.com"
},
"result": {
"status_code": 100
},
"transaction": {
"amount": 100,
"currency": "EUR",
"order_no": "123456"
}
}
And the controller:
#Value("classpath:/sample.json")
private Resource sampleResource;
#GetMapping(value = "/api")
public String mockMethod() throws IOException {
final InputStream inputStream = sampleResource.getInputStream();
final String sampleResourceString = StreamUtils.copyToString(sampleResource, StandardCharsets.UTF_8);
return sampleResourceString;
}
So basically, the application loads a JSON string from a file and returns it in the response. Now I'd like to replace the amount and the order_no in the JSON with a dynamic value, like this:
{
"customer": {
"email": "foo#bar.com"
},
"result": {
"status_code": 100
},
"transaction": {
"amount": ${amount},
"currency": "EUR",
"order_no": "${orderNumber}"
}
}
My first idea was to use Thymeleaf for this, so I created the following configuration:
#Configuration
#RequiredArgsConstructor
public class TemplateConfiguration {
private final ApplicationContext applicationContext;
#Bean
public SpringResourceTemplateResolver templateResolver() {
final SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("/templates");
templateResolver.setSuffix(".json");
templateResolver.setTemplateMode(TemplateMode.TEXT);
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
return templateEngine;
}
}
But I'm stuck how I can actually "run" the templating so that the sampleResourceString is replaced with the dynamic values.
Or is Thymeleaf maybe actually some kind of "overkill" for this?

Maybe just use String.replace() ?
#GetMapping(value = "/api")
public String mockMethod() throws IOException {
final InputStream inputStream = sampleResource.getInputStream();
final String sampleResourceString = StreamUtils.copyToString(sampleResource, StandardCharsets.UTF_8);
String replacedString = sampleResourceString.replace("${amount}", ...)
.replace("${orderNumber}", ...);
return replacedString;
}

Considering that the JSON has quite a simple structure, I would map them out to classes and use Jackson. For example:
public class DummyResponse {
private DummyResponseCustomer customer;
private DummyResponseTransaction transaction;
private DummyResponseResult result;
// TODO: Getters + Setters + ...
}
public class DummyResponseTransaction  {
private BigDecimal amount;
private String currency;
#JsonProperty("order_no")
private String orderNumber;
// TODO: Getters + Setters + ...
}
And then you could write a controller like this:
#ResponseBody // You need the #ResponseBody annotation or annotate the controller with #RestController
#GetMapping(value = "/api")
public DummyResponse mockMethod() {
return new DummyResponse(
new DummyResponseCustomer("foo#bar.com"),
new DummyResponseResult(100),
new DummyResponseTransaction(new BigDecimal("100"), "EUR", "123456")
);
}
This makes it fairly easy to come up with dynamic values and without having to use a templating engine (like Thymeleaf) or having to handle I/O. Besides that, you probably have these classes somewhere already to consume the external API.

Related

How do I parse snake case fields in a FeignClient response json?

I have configured a FeignClient in my spring boot webapp where I'm calling an external api that returns the following object.
public class Issue {
private Assignee assignee;
private Date createdAt;
private Date updatedAt;
private Date closedAt;
private String description;
private Date dueDate;
public Assignee getAssignee() {
return assignee;
}
public void setAssignee(Assignee assignee) {
this.assignee = assignee;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getDueDate() {
return dueDate;
}
public void setDueDate(Date dueDate) {
this.dueDate = dueDate;
}
public Date getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
public Date getClosedAt() {
return closedAt;
}
public void setClosedAt(Date closedAt) {
this.closedAt = closedAt;
}
#Override
public String toString() {
return (JacksonJson.toJsonString(this));
}
}
The fields updatedAt, createdAt and closedAt are all in snake case. All multi-word fields show up as null. Is there any way of configuring the FeignClient's Jackson parser so that it can process snake case characters? Note, that I cannot change the default Jackson Parser for my spring boot webapp because I myself render json in camel case. I just need to configure this parser on the FeignClient that I'm using to connect to an external REST api.
I have verified that the json response returned from the api call contains valid values in each of these json fields.
Here's how I solved it. I created a custom JacksonParser as a Spring Bean.
#Configuration(proxyBeanMethods = false)
public class FeignClientDateFormatConfig {
#Bean
public Decoder feignDecoder() {
HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
}
public ObjectMapper customObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
}
This successfully parses all snake case properties.
Please note that this has a severe limitation. If you have multiple FeignClients and only one of them returns snake-case json, then you're out of luck. This overrides the default FeignClient config. The only workaround possible with this solution is to move your FeignClient calls into a separate microservice so other FeignClient calls are not affected.

Spring boot consume 2 rest and merge some fields

Im new to Spring Boot and got a problem were i need to consume 2 remote Rest services and merge the results. Would need some insight on the right approach.
I got something like this:
{"subInventories":[
{"OrganizationId": 0,
"OrganizationCode":"",
"SecondaryInventoryName":"",
"Description":""},...{}...],
{"organizations":[
{"OrganizationId":0,
"OrganizationCode":"",
"OrganizationName":"",
"ManagementBusinessUnitId":,
"ManagementBusinessUnitName":""}, ...{}...]}
and need to make it into something like this:
{"items":[
{"OrganizationId":0,
"OrganizationCode":"",
"OrganizationName":"",
"ManagementBusinessUnitId":0,
"ManagementBusinessUnitName":"",
"SecondaryInventoryName":"",
"Description":""},...{}...]
got 2 #Entitys to represent each item, Organizations and Inventories with the attributtes like the JSON fields.
EDIT
Currently trying to get matches with Java8 stream()
#GetMapping("/manipulate")
public List<Organization> getManipulate() {
List<Organization> organization = (List<Organization>)(Object) organizationController.getOrganization();
List<SubInventories> subInventories = (List<SubInventories>)(Object) getSuvInventories();
List<Organization> intersect = organization.stream().filter(o -> subInventories.stream().anyMatch(s -> s.getOrganizationId()==o.getOrganizationId()))
.collect(Collectors.toList());
return intersect;
}
found this searching but i got many classes and I don't know if it would be better to just for each organization get the subinventories and put them in a list of maps like
List<Map<String,Object> myList = new ArrayList<>();
//Loops here
Map<String,Object> a = new HashMap<>();
a.put("OrganizationID", 1231242415)...
myList.add(a)
Quite lost in what the right approach is.
EDIT2
Here the classes I'm using.
Organizations
#Entity
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Organization implements Serializable{
//#JsonObject("OrganizationId")
#Id
private Long OrganizationId;
private Long ManagementBusinessUnitId;
private String OrganizationCode,OrganizationName,ManagementBusinessUnitName;
public Organization() {
}
//getters setters
}
SubInventories
#Entity
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class SubInventories implements Serializable{
#Id
private Long OrganizationId;
private String OrganizationCode,SecondaryInventoryName,Description;
public SubInventories() {
}
//getters and setters
}
Wrapper to unwrapp consume
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Wrapper {
//#JsonProperty("items")
private List<Object> items;
public Wrapper() {
}
public List<Object> getOrganization() {
return items;
}
public void setOrganization(List<Object> organization) {
this.items = organization;
}
}
OrganizationController
#RestController
public class OrganizationController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment env;
#GetMapping("/organizations")
public List<Object> getOrganization() {
return getOrganizationInfo();
}
private List<Object> getOrganizationInfo() {
String url = env.getProperty("web.INVENTORY_ORGANIZATIONS");
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url);
builder.queryParam("fields", "OrganizationId,OrganizationCode,OrganizationName,ManagementBusinessUnitId,ManagementBusinessUnitName");
builder.queryParam("onlyData", "true");
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(env.getProperty("authentication.name"),env.getProperty("authentication.password"));
HttpEntity request = new HttpEntity(headers);
ResponseEntity<Wrapper> temp = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, request, new ParameterizedTypeReference<Wrapper>() {});
List<Object> data = temp.getBody().getOrganization();
return data;
}
}
SubInventoryController
#RestController
public class SubInventoryController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment env;
#GetMapping("/sub")
public List<Object> getSuvInventories() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("this is private :(");
builder.queryParam("onlyData", "true");
builder.queryParam("expand", "subinventoriesDFF");
builder.queryParam("limit", "999999");
builder.queryParam("fields", "OrganizationId,OrganizationCode,SecondaryInventoryName,Description");
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(env.getProperty("authentication.name"),env.getProperty("authentication.password"));
headers.set("REST-Framework-Version", "2");
HttpEntity request = new HttpEntity(headers);
ResponseEntity<Wrapper> subInventories = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, request, new ParameterizedTypeReference<Wrapper>() {});
List<Object> data = subInventories.getBody().getOrganization();
return data;
}
}
where I'm right now
#RestController
public class MainController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment env;
#Autowired
private OrganizationController organizationController;
#Autowired
private SubInventoryController subInventoryController;
#GetMapping("/manipulate")
public Map<Organization, List<SubInventories>> getManipulate() {
List<Organization> organizations = (List<Organization>)(Object) organizationController.getOrganization();
List<SubInventories> subInventories = (List<SubInventories>)(Object) subInventoryController.getSuvInventories();
Map<Organization,List<SubInventories>> result = new HashMap<Organization,List<SubInventories>>();
for(Organization organization : organizations) {
List<SubInventories> subInventoryMatched = (List<SubInventories>) subInventories.stream().filter( s -> s.getOrganizationId()== organization.getOrganizationId()).collect(Collectors.toList());
result.put(organizations.get(0), subInventoryMatched);
}
return result;
}
}
From what I understand I need to make a wrapper class for each POJO cause the response looks like this
/organizations
{
"items": [
{
"OrganizationId": 1,
"OrganizationCode": "adasd",
"OrganizationName": "Hotel Bahía Príncipe Sunlight Costa Adeje",
"ManagementBusinessUnitId": 131231,
"ManagementBusinessUnitName": "asdasfdas"
},
{
"OrganizationId": 2,
"OrganizationCode": "adadas",
"OrganizationName": "Hadasd",
"ManagementBusinessUnitId": 1231,
"ManagementBusinessUnitName": "aewfrqaew"
}]}
and /subInventories
{
"items": [
{
"OrganizationId": 1,
"OrganizationCode": "asada",
"SecondaryInventoryName": "adfasdfasdgf",
"Description": "pub"
},
{
"OrganizationId": 2,
"OrganizationCode": "asgfrgtsdh",
"SecondaryInventoryName": "B LOB",
"Description": "pub2"
}
]}
If used the generic one with Object I get a java.lang.ClassCastException: java.util.LinkedHashMap incompatible with com.demo.model.Organization in the stream().filter and for the merge of the fields another class to get the desired
{
"items": [
{
"OrganizationId": 1,
"OrganizationCode": "asdas",
"OrganizationName": "adsadasd",
"ManagementBusinessUnitId": 1,
"ManagementBusinessUnitName": "asdasdf",
"SecondaryInventoryName": "sfsdfsfa",
"Description": "pub1"
}]}
Tons of classes if i get lots of POJO
I assume the following from the information you provide:
You have two Datatypes (Java classes). They should be merged together to one Java class
You have to load this data from different sources
Non of the classes are leading
I can provide you some example code. The code is based on the previos adoptions. This will give you an idea. It's not a simple copy and paste solution.
At first create a class with all fields you want to include in the result:
public class Matched {
private Object fieldA;
private Object fieldB;
// Some getter and Setter
}
The Basic idea is that you load your data. Than find the two corresponding objects. After that do your matching for each field.
public List<Matched> matchYourData() {
// load your data
List<DataA> dataAList = loadYourDataA();
List<DataB> dataBList = loadYourDataB();
List<Matched> resultList = new ArryList<>();
for (dataA: DataA) {
DataB dataB = dataBList.stream()
.filter(data -> data.getId() == dataA.getId())
.findFirst().orElseThrow();
// Now you have your data. Let's match them.
Matched matched = new Matched();
matched.setFieldA(dataB.getFieldA() == dataA.getFieldA() ? doSomething() : doSomethingElse());
// Set all your fields. Decide for everyone the matching strategy
resultList.add(matched);
}
return resultList;
}
This is a quite simple solution. Of course you can use Tools like Mapstruct for mapping purpose. But this depends on your environment.

Using a Wrapper Type for a DTO in Spring + Jackson

I'm trying to find a more or less elegant way to handle PATCH http operations in Spring MVC.
Basically, I'd like to perform a "dual" Jackson deserialization of a JSON document from a Request Body: one to a Map, and the other to the target POJO. Ideally, I would like to perform this in a single PartialDto<T> instance, where T is my target DTO type.
Better giving an example. Let's say I currently have this PUT mapping in a REST Controller:
#PutMapping("/resource")
public MyDto updateWhole(#RequestBody MyDto dto) {
System.out.println("PUT: updating the whole object to " + dto);
return dto;
}
My idea is to build a PartialDto type that would provide both POJO representation of the request body, as well as the Map representation, like this:
#PatchMapping("/resource")
public MyDto updatePartial(#RequestBody PartialDto<MyDto> partial) {
System.out.println("PATCH: partial update of the object to " + partial);
final MyDto dto = partial.asDto();
// Do stuff using the deserialized POJO
final Map<String, Object> map = partial.asMap();
// Do stuff as a deserialized map...
return dto;
}
I hope this will allow me to further expand the PartialDto implementation so I can perform things like this:
if (partial.hasAttribute("myAttribute")) {
final String myAttribute = dto.getMyAttribute();
// ...
}
Or even using a metamodel generator:
if (partial.hasAttribute(MyDto_.myAttribute)) {
final String myAttribute = dto.getMyAttribute();
// ...
}
So the question is simple: Jackson can easily map a JSON document to a POJO. It can also easily map a JSON document to a java Map. How can I do both at the same time in a Wrapper object such as my PartialDto?
public class PartialDto<T> {
private final Map<String, Object> map;
private final T dto;
PartialDto(Map<String, Object> map, T dto) {
this.map = map;
this.dto = dto;
}
public T asDto() {
return this.dto;
}
public Map<String, Object> asMap() {
return Collections.unmodifiableMap(this.map);
}
}
I tried to use a GenericConverter like this (that, of course, I registered in Spring MVC's FormatterRegistry):
public class PartialDtoConverter implements GenericConverter {
private final ObjectMapper objectMapper;
public PartialDtoConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, PartialDto.class));
}
#Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
final Class<?> targetClazz = targetType.getResolvableType().getGeneric(0).getRawClass();
final Map<String, Object> map;
try {
map = objectMapper.readValue((String) source, Map.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e); // FIXME
}
final Object dto = objectMapper.convertValue(map, targetClazz);
return new PartialDto(map, dto) ;
}
}
And this converter works well when tested directly using Spring's ConversionService:
#SpringBootTest
class ConverterTest {
#Autowired
private ConversionService conversionService;
#Test
public void testPartialUpdate() throws Exception {
final MyDto dto = new MyDto()
.setIt("It");
final PartialDto<MyDto> partialDto = (PartialDto<MyDto>) conversionService.convert(
"{ \"it\": \"Plop\" }",
new TypeDescriptor(ResolvableType.forClass(String.class), null, null),
new TypeDescriptor(ResolvableType.forClassWithGenerics(PartialDto.class, MyDto.class), null, null)
);
Assertions.assertEquals("Plop", partialDto.asDto().getIt());
Assertions.assertEquals("Plop", partialDto.asMap().get("it"));
}
}
However, it doesn't seem to work in a #RequestBody such as shown above. Reminder:
#PatchMapping("/resource")
public MyDto updatePartial(#RequestBody PartialDto<MyDto> partial) {
// ...
}
Any idea is welcome.

Spring MVC Test with RestTemplate: Generic collection fails (even with ParameterizedTypeReference)

I am working with Spring Framework 4.3.1
I have the following domain class
#XmlRootElement(name="persona")
#XmlType(propOrder = {"id","nombre","apellido","fecha"})
public class Persona implements Serializable {
#XmlElement(name="id")
#JsonProperty("id")
public String getId() {
return id;
}
....
Where each getter has the #XmlElement and #JsonProperty annotations.
I am working with JAXB2 and Jackson2
I have the following too:
#XmlRootElement(name="collection")
public class GenericCollection<T> {
private Collection<T> collection;
public GenericCollection(){
}
public GenericCollection(Collection<T> collection){
this.collection = collection;
}
#XmlElement(name="item")
#JsonProperty("collection")
public Collection<T> getCollection() {
return collection;
}
public void setCollection(Collection<T> collection) {
this.collection = collection;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
for(Object object : collection){
builder.append("[");
builder.append(object.toString());
builder.append("]");
}
return builder.toString();
}
}
About Testing, the many #Tests methods working through Spring MVC Test work fine. The #Controller and #RestController work how is expected.
Note: I can test the CRUD scenarios, it about the HTTP methods such as POST, PUT, GET and DELETE. Therefore I am able to get one entity and a collection of entities.
Note: from the previous note, all works working around the XML and JSON formats.
Now trying to do testing through the RestTemplate how a kind of programmatic client, it only fails for collections. With the following:
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(httpMessageConverterConfig.marshallingMessageConverter());
converters.add(httpMessageConverterConfig.mappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(converters);
System.out.println("converters.size():" + converters.size());
}
I can confirm converters.size() always prints 2
The following is for XML and JSON
#Test
public void findAllXmlTest(){
RequestEntity<Void> requestEntity = RestControllerSupport_.createRequestEntityForGet(uri, retrieveURI);
ParameterizedTypeReference<GenericCollection<Persona>> parameterizedTypeReference = new ParameterizedTypeReference<GenericCollection<Persona>>(){};
ResponseEntity<GenericCollection<Persona>> responseEntity = restTemplate.exchange(requestEntity, parameterizedTypeReference);
assertThat(responseEntity, notNullValue());
assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK));
assertThat(responseEntity.getHeaders().getContentType(), is(MediaType.APPLICATION_XML) );
assertThat(responseEntity.getBody(), notNullValue());
assertThat(responseEntity.getBody().getClass(), is(GenericCollection.class));
assertThat(responseEntity.getBody().getCollection(), is(personas));
}
#Test
public void findAllJsonTest(){
RequestEntity<Void> requestEntity = RestControllerSupport_.createRequestEntityForGet(uri, retrieveURI);
ParameterizedTypeReference<GenericCollection<Persona>> parameterizedTypeReference = new ParameterizedTypeReference<GenericCollection<Persona>>(){};
ResponseEntity<GenericCollection<Persona>> responseEntity = restTemplate.exchange(requestEntity, parameterizedTypeReference);
assertThat(responseEntity, notNullValue());
assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK));
assertThat(responseEntity.getHeaders().getContentType(), is(MediaType.APPLICATION_JSON_UTF8) );
assertThat(responseEntity.getBody(), notNullValue());
assertThat(responseEntity.getBody().getClass(), is(GenericCollection.class));
assertThat(responseEntity.getBody().getCollection(), is(personas));
}
Note: observe I am using ParameterizedTypeReference for both scenarios.
For JSON it works.
But for XML I get:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [com.manuel.jordan.controller.support.GenericCollection<com.manuel.jordan.domain.Persona>] and content type [application/xml]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:109)
What is wrong or missing?
Your problem that you use MarshallingHttpMessageConverter which isn't GenericHttpMessageConverter, like it is expected for the ParameterizedTypeReference in the HttpMessageConverterExtractor:
if (messageConverter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<?> genericMessageConverter =
(GenericHttpMessageConverter<?>) messageConverter;
if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
The MappingJackson2HttpMessageConverter is that one.
So, I suggest you to try with Jaxb2CollectionHttpMessageConverter.

error in parsing JSON using GSON with retrofit API

this is POJO or model class that I am using, I'm able to get the response from server but it is throwing SyntaxException and MalformedJSONException
//Model class:
public class AppUser {
public Integer userID;
public String username;
public String password;
public String emailID;
public String accessToken;
}
//JSON Retrieved:
{
"userID": "123456",
"username": "rkumar",
"password": "rohitk",
"emailID": "rohitk#gmail.com",
"accessToken": "1weErtYo90ds8i9"
}
//JSON Deserializer
public class AppUserDeserializer implements JsonDeserializer<AppUser> {
#Override
public AppUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Log.d("Json element",json.toString());
return new Gson().fromJson(json, AppUser.class);
}
}
//Rest Client Constructor
public RestClient()
{
Gson gson = new GsonBuilder()
.registerTypeAdapter(AppUser.class, new AppUserDeserializer())
.create();
RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(baseURL)
.setConverter(new GsonConverter(gson))
.build();
signInService = restAdapter.create(SignInService.class);
}

Resources