Spring MVC - get only a single object from an external json - json

i need to consume a RESTApi, which gives me a JSON output like this
{
"id": "e5d5ccc0-8da4-430d-b0ec-096d17ae2af8",
"car": [
{
"identifier": "XX000YY",
"formattedAddress": "Address 1",
"lat": 45.841664,
"lng": 18.199905,
"isOn": false,
"odometer": 763.4,
}
],
"location": "92589f4a-8c6e-4494-8548-b5428f8fa598"
}
Usually i would create a Wrapper Object
public class Wrapper{
private String id;
private Car car;
private String location;
//getters and setters
}
Then in my controller i would do something like this
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Wrapper> response = restTemplate.exchange(url, HttpMethod.POST, httpEntity,wrapper);
to get the response
But basically i need only the Car object, so i was thinking if there was a way to just return the it instead of the whole wrapper and taking the Car objects from it

First of all, you must create a serializable object like this :
Car.java
package com.wrapper;
public class Car implements Serializable{
public String identifier;
public String formattedAddress;
public Float lat;
public Float lng;
public Boolean isOn;
public Float odometer;
}
Wrapper.java
package com.wrapper;
import java.util.List;
public class Wrapper implements Serializable{
public String id;
public List<Car> car = null;
public String location;
}
After that, use a google API gson and use this instruction to get an object :
Wrapper wrapper = gson.fromJson(response, Wrapper.class);

Related

How to specify the endoint of a REST API accepting a list of types using a supertype?

I intend to specify a REST endpoint in a Spring Boot application which accepts a list of objects (List<? extends SuperObject>). SuperObject is an abstract base class. The lists sent within the RequestBody of an HttpRequest keeps instances of SuperObject's sub-/childclasses (SubType1OfSuperObject and SubType2OfSuperObject).
Currently I tried this:
#PostMapping(path = "store", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public String store(#RequestBody List<? extends SuperObject> oject, #RequestParam Technology technology) {
theService.saveObjectsByTechnology(objects, technology);
return "Perfect!";
}
I tried to send a list of subtype objects using Postman:
{
"subtype1OfSuperObjects": [
{
"prop1": 4,
"prop2": "foo",
"prop3": "xxx"
},
{
"prop1": 7,
"prop2": "Bar",
"prop3": "zzz"
}
]
}
The result is
{
"timestamp": "2021-08-18T12:24:55.797+00:00",
"status": 400,
"error": "Bad Request",
"path": "/admin/error/store"
}
The SuperObject class:
#Data
#SuperBuilder
#NoArgsConstructor
#Validated
public abstract class SuperObject {
private Integer prop1;
private String prop2;
}
The SubType1OfSuperObject class:
#Data
#SuperBuilder
#NoArgsConstructor
#Validated
public class SubType1OfSuperObject extends SuperObject {
private String prop3;
}
The SubType1OfSuperObject class:
#Data
#SuperBuilder
#NoArgsConstructor
#Validated
public class SubType2OfSuperObject extends SuperObject {
private String prop4;
}
How can I achieve this. The classes of the supertype and the subtypes are implemented, of course.
The REST API is accessible as I can invoke another (GET) endpoint (to list all subtypes, for instance).
If I understand it well you have a list of data to send? if that's the case you can have something like below:
In your controller
#PostMapping("/")
#ResponseBody
public String save(#RequestBody List<Data> data){
System.out.println(data);
return "good";
}
And you define your Data pojo like this
class Data{
private List<SubData> subtype1OfSuperObjects;
//getters, setters and toString
}
class SubData{
private int prop1;
private String prop2;
// getters, setters and toString
}
Here is the result of by sysout
[Data{subtype1OfSuperObjects=[SubData{prop1=4, prop2='foo'}, SubData{prop1=7, prop2='Bar'}]}]

Mocking static methods objects with Mockito/powermock

I have a class DistributionRule class which contains private Set distributions;
#Getter
#Setter
public class DistributionRule extends BaseModel {
private String ruleName;
private String skuId;
private String catalogueId;
private String categoryId;
private Boolean active;
private RuleLevel level;
private Set<Distribution> distributions;
private Double thresholdValue;
private RuleType ruleType = RuleType.GENERAL;
#Getter
#Setter
public static class Distribution {
private String consumerId;
private Double distribution;
}
}
In RuleServiceImpl, there is a method to get DistributionRule list:
#Override
public List<DistributionRule> getDistributionRules() {
return ruleRepository.findAll();
}
In DivRuleApplicationTests class:
#RunWith(SpringRunner.class)
#SpringBootTest
class DivRuleApplicationTests {
#Autowired
private RuleServiceImpl ruleServiceImpl;
#MockBean
RuleRepository ruleRepository;
#Test
void test() {
assertTrue(true);
}
#Test
public void getDistributionRulesTest(){
Set<Distribution> distributions = new HashSet();
when(ruleRepository.findAll()).thenReturn(Stream
.of(new DistributionRule("rule0010", "0010", " ", " ", true, "SKU", ""),
new DistributionRule("rule0020", "0020", "", "", true, "SKU", ""))
.collect(Collectors.toList()));
assertEquals(2, ruleServiceImpl.getDistributionRules().size());
}
}
How do I pass the "Distribution" values in Stream.of()?
Firstly, the method you are trying to mock on when, returns a List<DistributionRule>. In this case, you don't need to create a Stream.of(new DistributionRule(...)) and then collect it as a List. Simply use List.of(new DistributionRule(...)), as it will return the List you want it.
Secondly, you should annotate both DistributionRule and inner class Distribution with #AllArgsConstructor to generate a constructor with all arguments. This will let you create an DistributionRule object passing the Distribution Set as a parameter:
#Test
public void getDistributionRulesTest(){
Set<Distribution> distributions1 = Set.of(
new Distribution(...), new Distribution(...)
);
Set<Distribution> distributions2 = Set.of(
new Distribution(...), new Distribution(...)
);
when(ruleRepository.findAll()).thenReturn(List.of(
new DistributionRule("rule0010", "0010", ..., distributions1, ..),
new DistributionRule("rule0020", "0020", ..., distributions2, ...)
));
assertEquals(2, ruleServiceImpl.getDistributionRules().size());
}
PS: I usually use the #Data annotation instead of #Getter and #Setter, as it's a shortcut for that and other annotations. You can take a look here if you want to know more: https://projectlombok.org/features/Data

How to combine #JsonView with #JsonProperty?

I have a DTO class that should serve json via a spring-mvc #RestController.
I want to provide different version/views on the same object. Especially, there are fields that are only used in VERSION_1 of the api, and some only in VERSION_2.
Problem: I could add #JsonView for this, but my goal is also to rename those fields. Some fields should actually replace the same name from previous versions.
Example:
public class Person {
#JsonView(View.Version_1.class)
#JsonProperty("name")
private String name; //eg only the firstname
#JsonView(View.Version_2.class)
#JsonProperty("name")
private NameDTO namedto; //now changing to first+last name
static class NameDTO {
private String firstname;
private String lastname;
}
}
#RestController
public class MyServlet {
#GetMapping("/person/{id}")
#JsonView(View.Version_1.class)
public PersonDTO person1(int id) {
//...
}
#GetMapping("/person_new/{id}")
#JsonView(View.Version_2.class)
public PersonDTO person2(int id) {
//...
}
}
So, depending on the view/version, you would get the same json field firstname, but with different content.
In this example, using V1 would give:
{"name": "john"}
Whereas using V2 should result in:
{"name": {"firstname": "john", "lastname": "doe"}}
BUT not with he code above, as jackson complains:
com.fasterxml.jackson.databind.JsonMappingException: Conflicting
getter definitions for property "name".
Is that possible at all?
I found a way using:
https://github.com/jonpeterson/spring-webmvc-model-versioning
Basic idea is to add a custom VersionedModelConverter that is applied on #VersionedModelConverter annotated webservice response classes.
#Configuration
#Import(VersionedModelResponseBodyAdvice.class)
public class SpringMvcVersioningConfiguration {
//register in jackson. spring-boot automatically registers any module beans
#Bean
public Model versioningModel() {
return new VersioningModule();
}
}
#GetMapping
#VersionedResponseBody(defaultVersion = "2.0")
public Person person() {
}
#JsonVersionedModel(currentVersion = "3.0" toPastConverterClass = PersonConverter.class)
public class Person {
}
public class PersonConverter implements VersionedModelConverter {
#Override
public ObjectNode convert(ObjectNode modelData, String modelVersion, String targetModelVersion, JsonNodeFactory nodeFactory) {
Double modelv = Double.valueOf(modelVersion);
Double targetv = Double.valueOf(targetVersion);
//todo if-else based on model version
Object node = modelData.remove("fieldname");
//node.change...
modelData.set("fieldname_renamed", node);
}
}

#JsonAnySetter, #JsonAnyGetter with DSL Json (De)/Serialization not taking values(always null)

Im using #JsonAnySetter and #JsonAnyGetter in my POJO class using my Custom serialization with DSL JSON class, the Map is initialized but the other properties are always null.
My POJO class:
#CompiledJson
public class Name {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
Map<String,String> properties = new HashMap<String,String>();
public Name() {
// TODO Auto-generated constructor stub
}
#JsonAnyGetter
public Map<String, String> get() {
return this.properties;
}
#JsonAnySetter
public void set(String key, String value) {
this.properties.put(key, value);
}
De/Serializing using DSLJson serialize() and deserialize() methods. I do not see any error also, but properties remains null in JSON. I doubt if Jackson annotations are supported by DSL Json. :/
Spring Boot App with DSL Json and Jackson Annotations
UPDATE
I want to parse MyClass, which is a part of RootClass:
#Compiledjson
public class RootClass {
private String id;
private List<MyClass> myclass;
private AnotherCLass class2;
//getters and setter here
}
#CompiledJson
public class MyClass implements JsonObject {
private String name;
private Map<String, String> properties; //want this to behave like Jackson's #JsonAnySetter/Getter annotation.
//The implementation of MapConverter serializer you mentioned below.
}
The entire code parses through custom Message reader and writer.
While sending my JSON Body, It'll be like this :
{
"id" : "1234",
"myclass" :
[
{
"name" : "abcd",
//any dynamic properties I want to add will go here
"test" : "test1",
"anything" : "anything"
}
],
"class2" : "test5"
}
Thank you :)
DSL-JSON doesn't support such get()/set(string, string) method pairs.
It does understand Map<String, String> so if you expose properties it will work on that. But not in this kind of setup.
As of v1.1 you have two options for solving such problems, both of them are covered in example project
If you wish to reuse existing converters, your solution can look like this:
public static class MyClass {
private String name;
private Map<String, String> properties;
#JsonConverter(target = MyClass.class)
public static class MyClassConverter {
public static final JsonReader.ReadObject<MyClass> JSON_READER = new JsonReader.ReadObject<MyClass>() {
public MyClass read(JsonReader reader) throws IOException {
Map<String, String> properties = MapConverter.deserialize(reader);
MyClass result = new MyClass();
result.name = properties.get("name");
result.properties = properties;
return result;
}
};
public static final JsonWriter.WriteObject<MyClass> JSON_WRITER = new JsonWriter.WriteObject<MyClass>() {
public void write(JsonWriter writer, MyClass value) {
MapConverter.serialize(value.properties, writer);
}
};
}
}

Spring MVC + Jackson: field not being serialized

I am trying to make a simple round-trip with a REST API that leads to storing an entity into the db and then returns the stored entity.
Going down works fine and the entity is stored and correctly returned to the REST Controller. However, when I return it, Jackson seems to serialize it incorrectly, as the "name" attribute is not included.
This is the entity:
#Entity
#Configurable
public class MyEntity extends IdentifiableEntity {
private String name;
protected MyEntity() {
};
public MyEntity(String name) {
this.name = name;
}
}
and the extended entity:
#Configurable
#Inheritance(strategy = InheritanceType.JOINED)
#Entity
public abstract class IdentifiableEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version = 1;
public String toString() {
return ReflectionToStringBuilder.toString(this,
ToStringStyle.SHORT_PREFIX_STYLE);
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getVersion() {
return this.version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
The REST controller is:
#RestController
#RequestMapping("/service")
public class Service {
#RequestMapping(value = "/public/{name}", method = RequestMethod.GET)
public MyEntity storeEntityPublic(#PathVariable String name) {
System.out.println("Hello " + name
+ ", I am saving on the db. (PUBLIC)");
MyEntity saved = controller.saveEntity(name);
return saved;
}
}
Then my business logic:
#Service
public class LogicController {
#Autowired
private MyEntityRepository myEntityRepository;
public MyEntity saveEntity(String name) {
MyEntity cg = new MyEntity(name);
return myEntityRepository.save(cg);
}
}
I am using Spring repositories:
#Repository
public interface MyEntityRepository extends JpaSpecificationExecutor<MyEntity>,
JpaRepository<MyEntity, Long> {
}
The returned JSON is:
{"id":12,"version":1}
Where is my "name" attribute? Is is set in the variable being returned by the REST controller.
I found the trick: MyEntity needs to have a public get for the property that has to be shown. A good reason to use a DTO pattern.
In response to your "I don't want to have my Entity "dirty"" comment: Jackson allows the use of so-called Mixins. They allow you to define annotations for your class outside the class itself. In your case it could look like this:
public abstract class MyEntityMixin {
#JsonProperty
private String name;
}
You may keep it as a field and annotate the field with #JsonProperty if you like.