Swagger-UI with java - model not being generated in inheritance structure - java

I got a webservice that consumes the following structure:
#ApiModel(discriminator = "status", subTypes = {SuccessPolicyUpdate.class, FailurePolicyUpdate.class})
public abstract class AbstractPolicyUpdate {
private String clientId;
private PolicyStatus status;
}
this is being inherited by the followign twso subclasses
#ApiModel(parent = AbstractPolicyUpdate.class)
public class FailurePolicyUpdate extends AbstractPolicyUpdate {
private FailureCode code;
}
#ApiModel(parent = AbstractPolicyUpdate.class)
public class SuccessPolicyUpdate extends AbstractPolicyUpdate {
private String policyNumber;
private List<Inconsistency> inconsistencies = new ArrayList<>();
}
And inconsistency is just a pojo:
public class Inconsistency {
private InconsistencyCode code;
private String oldValue;
private String newValue;
}
this generates the following json:
{"swagger":"2.0","info":{"description":"Api Documentation","version":"1.0","title":"Api Documentation","termsOfService":"urn:tos","contact":{},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0"}},"host":"localhost:8080","basePath":"/","tags":[{"name":"digital-vapz-controller","description":"Digital Vapz Controller"}],"paths":{"/api/policy":{"post":{"tags":["digital-vapz-controller"],"summary":"Update policy status","operationId":"updatePolicyStatusUsingPOST","consumes":["application/json"],"produces":["application/json"],"parameters":[{"name":"Authorization","in":"header","description":"Authorization","required":true,"type":"string"},{"in":"body","name":"policyUpdate","description":"policyUpdate","required":true,"schema":{"$ref":"#/definitions/AbstractPolicyUpdate"}}],"responses":{"200":{"description":"Request succesfully processed"},"201":{"description":"Created"},"400":{"description":"Invalid input parameters for this request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"Invalid authorization for this request"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal server error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"deprecated":false}}},"definitions":{"AbstractPolicyUpdate":{"type":"object","discriminator":"status","properties":{"clientId":{"type":"string"},"status":{"type":"string","enum":["SUCCESS","FAILURE"]}},"title":"AbstractPolicyUpdate"},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string"},"errors":{"type":"array","items":{"type":"string"}}},"title":"ErrorResponse"},"FailurePolicyUpdate":{"title":"FailurePolicyUpdate","allOf":[{"$ref":"#/definitions/AbstractPolicyUpdate"},{"type":"object","properties":{"clientId":{"type":"string"},"code":{"type":"string","enum":["CLIENT_ONBOARDING_KNOWN","CLIENT_RSB_CODE","CLIENT_VAPZ_EXISTS","RETRIEVING_PRODUCT","RETRIEVING_PTC","NEEDS_ANALYSIS","CREATING_POLICY","UPDATE_PTC"]},"status":{"type":"string","enum":["SUCCESS","FAILURE"]}},"title":"FailurePolicyUpdate"}]},"SuccessPolicyUpdate":{"title":"SuccessPolicyUpdate","allOf":[{"$ref":"#/definitions/AbstractPolicyUpdate"},{"type":"object","properties":{"clientId":{"type":"string"},"inconsistencies":{"type":"array","items":{"$ref":"#/definitions/Inconsistency"}},"policyNumber":{"type":"string"},"status":{"type":"string","enum":["SUCCESS","FAILURE"]}},"title":"SuccessPolicyUpdate"}]}}}
There is a ref to Inconsistency, but the model itself is not in the generated json. What am I forgetting?
I am using io.springfox libs, version 2.9.2

Related

Using #JsonCreator to create two instances of same class in one JSON DTO

I would like to deserialize JSON of this structure:
{
"employee_pricing_type":"COMPUTE_BY_OWN_RATE",
"employee_rate":10,
"customer_pricing_type":"COMPUTE_BY_OWN_RATE",
"customer_rate":200
}
I have such POJO to create price setting from a HTTP request:
public class ObjectPricingSetting {
#JsonProperty("pricing_type") // describes output
private final ObjectPricingType pricingType;
#JsonProperty("own_rate") // describes output
private final BigDecimal ownRate;
public ObjectPricingSetting(final ObjectPricingType pricingType, final BigDecimal ownRate) {
AssertUtils.notNull(pricingType, "pricingType");
this.pricingType = pricingType;
if (ownRate != null) {
AssertUtils.isGtZero(ownRate, "ownRate");
this.ownRate = ownRate;
} else {
this.ownRate = null;
}
}
public ObjectPricingType getPricingType() {
return pricingType;
}
public BigDecimal getOwnRate() {
return ownRate;
}
}
this is DTO:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ObjectPricingCommand extends BaseDto<ObjectId> {
#JsonProperty(value = "employee_pricing_setting")
private ObjectPricingSetting employeePricingSetting;
#JsonProperty(value = "customer_pricing_setting")
private ObjectPricingSetting customerPricingSetting;
}
I would like to create these two instances of ObjectPricingSetting with #JsonCreator.
Q: How should I anotate #JsonProperty parameter in ObjectPricingSetting constructor to recognize what JSON value should use to create these two instances?
You can use #JsonUnwrapped with a prefix in your parent class:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ObjectPricingCommand extends BaseDto<ObjectId> {
#JsonUnwrapped(prefix = "employee_")
private ObjectPricingSetting employeePricingSetting;
#JsonUnwrapped(prefix = "customer_")
private ObjectPricingSetting customerPricingSetting;
}
Then you can use the normal #JsonCreator/#JsonProperty in your nested DTO, without the prefix:
public class ObjectPricingSetting {
#JsonCreator
public ObjectPricingSetting(
#JsonProperty("pricing_type") final ObjectPricingType pricingType,
#JsonProperty("rate") final BigDecimal ownRate) {
...

Deserialize two different JSON representations into one object

I have Java class like
#Data
public class Comment {
private Integer id; // should be used anyhow
private Long refId; // for internal purpose -> not be serialized
private String text; // should be used in QuickComment
private String patch; // should be included in PatchComment ONLY
private String status; // should be included in StatusComment ONLY
}
and I have
#Data
public class Response{
private Comment statusComment;
private Comment patchComment;
}
I thought about using JsonView like
public class Views{
public interface StatusComment{}
public interface PatchComment{}
}
and apply them to the inital class
#Data
public class Comment {
#JsonView({Views.StatusComment.class, Views.PatchComment.class})
private Integer id; // should be used anyhow
private Long refId; // for internal purpose -> not be serialized
#JsonView({Views.StatusComment.class, Views.PatchComment.class})
private String text; // should be used anyhow
#JsonView(Views.PatchComment.class)
private String patch; // should be included in PatchComment ONLY
#JsonView(Views.StatusComment.class)
private String status; // should be included in StatusComment ONLY
}
and the Response
#Data
public class Response{
#JsonView(Views.StatusComment.class)
private Comment statusComment;
#JsonView(Views.PatchComment.class)
private Comment patchComment;
}
But somehow it fails completely. It fails completly, ie. nothing is filtered. Is it problem with Lombok. Or is it defined incorrect?
How do you serialize your objects? Are you using Spring? Are you using the ObjectMapper directly?
If you're using Spring then what you need to do is annotate method of your controllers with #JsonView(Views.StatusComment.class) or #JsonView(Views.PatchComment.class) like:
For reading GET endpoints
#JsonView(Views.StatusComment.class)
#RequestMapping("/comments/{id}")
public Comment getStatusComments(#PathVariable int id) {
return statusService.getStatuscommentById(id);
}
For writing:
#RequestMapping(value = "/persons", consumes = APPLICATION_JSON_VALUE, method = RequestMethod.POST)
public Comment saveStatusComment(#JsonView(View.StatusComment.class) #RequestBody Comment c) {
return statusService.saveStatusComment(c);
}
If you're using the ObjectMapper directly, then what you need to do is specify the used View:
When writing:
ObjectMapper mapper = new ObjectMapper();
String result = mapper
.writerWithView(Views.StatusComment.class)
.writeValueAsString(comment);
When reading:
ObjectMapper mapper = new ObjectMapper();
Comment comment = mapper
.readerWithView(Views.StatusComment.class)
.forType(Comment.class)
.readValue(json);

Missing name, in state: START_OBJECT parsing XML using Jackson

I'm trying to parse some XML that looks like this:
<correlationMatrix>
<assetMatrix numAssets="45">
<correlations asset="Name1" />
<correlations asset="Name2">
<correlation asset="Name3">1.23</correlation>
</correlations>
<correlations asset="Name4">
<correlation asset="Name5">2.34</correlation>
<correlation asset="Name6">3.45</correlation>
</correlations>
</assetMatrix>
</correlationMatrix>
I've created 3 classes:
#JsonIgnoreProperties(ignoreUnknown = true)
public class CorrelationMatrix {
private List<Correlations> assetMatrix;
public List<Correlations> getAssetMatrix() {
return assetMatrix;
}
public void setAssetMatrix(List<Correlations> assetMatrix) {
this.assetMatrix = assetMatrix;
}
}
And
#JsonIgnoreProperties(ignoreUnknown = true)
public class Correlations {
private String asset;
private List<Correlation> correlation;
public String getAsset() {
return asset;
}
public void setAsset(String asset) {
this.asset = asset;
}
public List<Correlation> getCorrelation() {
return correlation;
}
public void setCorrelations(List<Correlation> correlation) {
this.correlation = correlation;
}
}
Then finally
#JsonIgnoreProperties(ignoreUnknown = true)
public class Correlation {
}
As you can see I've removed everything from the final inner class, but it still fails to parse. I've tried removing <correlations asset="Name1" /> from the input but that's not the source of the problem. If I remove private List<Correlation> correlation; from Correlations then that does then parse successfully but obviously doesn't have the information I need.
What is it that I need to do differently here to parse what is essentially a 2 dimensional array from XML into Java using Jackson (2.2.0 if that matters)?
The error I get is:
Missing name, in state: START_OBJECT (through reference chain: CorrelationMatrix["assetMatrix"]->Correlations["correlation"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(
Update:
The problem seems to be associated with the values inside correlation. If I remove 1.23, 2.34 and 3.45 from my example data then it parses - so I need to somehow tell Jackson how to map them.
I was able to parse all the elements in the example xml with these modified classes (add getters, setters and use correct name setCorrelation in Correlations):
class CorrelationMatrix {
private AssetMatrix assetMatrix;
}
class AssetMatrix {
#JacksonXmlProperty(isAttribute = true)
private int numAssets;
#JacksonXmlElementWrapper(useWrapping = false)
private List<Correlations> correlations;
}
class Correlations {
#JacksonXmlProperty(isAttribute = true)
private String asset;
#JacksonXmlElementWrapper(useWrapping = false)
private List<Correlation> correlation;
}
class Correlation {
#JacksonXmlProperty(isAttribute = true)
private String asset;
#JacksonXmlText
private double correlation;
}
I didn't need #JsonIgnoreProperties(ignoreUnknown = true) anywhere
#JacksonXmlProperty(isAttribute = true) is needed for attributes like asset and numAssets
There are 2 types of lists in the xml that are both unwrapped so specify it with this #JacksonXmlElementWrapper(useWrapping = false)
You can parse the innermost double numbers with this #JacksonXmlText although the field in Java is not text.
I introduced a wrapper class AssetMatrix to capture numAssets

Unable to parse json with List elements inside it

I have a Java class which has 2 List Object inside it and i am Json serializing the parent class.
#JsonSerialize
public class RequestSalesJson {
#JsonProperty("nonUniqueSalesList")
private List<SalesDataJson> getNonUniqueSalesDataJson;
#JsonProperty("uniqueSalesList")
private List<SalesDataJson> uniqueSalesDataJson;
public List<SalesDataJson> getGetNonUniqueSalesDataJson() {
return getNonUniqueSalesDataJson;
}
public void setGetNonUniqueSalesDataJson(List<SalesDataJson> getNonUniqueSalesDataJson) {
this.getNonUniqueSalesDataJson = getNonUniqueSalesDataJson;
}
public List<SalesDataJson> getUniqueSalesDataJson() {
return uniqueSalesDataJson;
}
public void setUniqueSalesDataJson(List<SalesDataJson> uniqueSalesDataJson) {
this.uniqueSalesDataJson = uniqueSalesDataJson;
}
}
SalesReturnJson.java
#JsonSerialize
public class SalesReturnJson {
#JsonProperty("starttime")
private String startTime;
#JsonProperty("pn")
private String partNumber;
#JsonProperty("so")
private String SalesOrderNumber;
#JsonProperty("wo")
private String workOrderNumber;
#JsonProperty("loc")
//other variables declared..
}
Controller.java :-
#RequestMapping(value = "/addAllSalesData",method = RequestMethod.POST)
public void addAllSalesData(#RequestBody RequestSalesJson requestSalesJsons){
log.info("POST : '/addSalesData'");
try{
System.out.print("In Controller "+requestSalesJsons.getUniqueSalesDataJson());
//salesService.processSalesData(requestSalesJsons);
}
catch(Exception e){
// return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}
The value here is coming to be null.
Below is the json i am using :-
{ "uniqueSalesJson": [{"SO":4000955,"Part Number":"000","Locator":255638,"Lot Number":"P01-2059139","Reservation Quantity":2,"Status":"Released to warehouse","COE":"Fabrication","ORG":"P07","Start_Time":"2017-09-19 11:21:36"},{"SO":4000955,"Part Number":"000","Locator":255652,"Lot Number":"P01-2059140","Reservation Quantity":10,"Status":"Released to warehouse","COE":"Fabrication","ORG":"P07","Start_Time":"2017-09-19 11:21:36"}],"nonUniqueSalesJson":[{"SO":4000992,"Part Number":"1276M84G15","Locator":12345,"Lot Number":"P01-2344141","Reservation Quantity":6,"Status":"PACKED","COE":"Fabrication","ORG":"P07","Start_Time":"2017-09-19 11:21:36"},{"SO":4000992,"Part Number":"1276M84G15","Locator":12345,"Lot Number":"P01-2344141","Reservation Quantity":6,"Status":"PICKED","COE":"Fabrication","ORG":"P07","Start_Time":"2017-09-19 11:21:36"}]}
There are some issues in your code that let me doubt that your application compiles. First of all, rename the SalesReturnJson class to SalesDataJson.
Then check your #JsonProperty annotations. The value here must match exactly the property key in the Json String. Refactoring all this stuff will lead you to your root entity class:
#JsonSerialize
public class RequestSalesJson {
#JsonProperty("nonUniqueSalesJson")
private List<SalesDataJson> nonUniqueSalesDataJson;
#JsonProperty("uniqueSalesJson")
private List<SalesDataJson> uniqueSalesDataJson;
...
}
and your SalesDataJson class (missing a lot of attributes which the mapper ignores by configuration):
#JsonSerialize
public class SalesDataJson {
#JsonProperty("Start_Time")
private String startTime;
#JsonProperty("Part Number")
private String partNumber;
#JsonProperty("SO")
private String SalesOrderNumber;
}
This sample works as expected with the com.fasterxml.jackson.databind.ObjectMapper
Hope that helps!

Spring HATEOAS with nested resources and JsonView filtering

I am trying to add HATEOAS links with Resource<>, while also filtering with #JsonView. However, I don't know how to add the links to nested objects.
In the project on on Github, I've expanded on this project (adding in the open pull request to make it work without nested resources), adding the "Character" entity which has a nested User.
When accessing the ~/characters/resource-filtered route, it is expected that the nested User "player" appear with the firstNm and bioDetails fields, and with Spring generated links to itself, but without the userId and lastNm fields.
I have the filtering working correctly, but I cannot find an example of nested resources which fits with the ResourceAssembler paradigm. It appears to be necessary to use a ResourceAssembler to make #JsonView work.
Any help reconciling these two concepts would be appreciated. If you can crack it entirely, consider sending me a pull request.
User.java
//package and imports
...
public class User implements Serializable {
#JsonView(UserView.Detail.class)
private Long userId;
#JsonView({ UserView.Summary.class, CharacterView.Summary.class })
private String bioDetails;
#JsonView({ UserView.Summary.class, CharacterView.Summary.class })
private String firstNm;
#JsonView({ UserView.Detail.class, CharacterView.Detail.class })
private String lastNm;
public User(Long userId, String firstNm, String lastNm) {
this.userId = userId;
this.firstNm = firstNm;
this.lastNm = lastNm;
}
public User(Long userId) {
this.userId = userId;
}
...
// getters and setters
...
}
CharacterModel.java
//package and imports
...
#Entity
public class CharacterModel implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(CharacterView.Summary.class)
private Long characterId;
#JsonView(CharacterView.Detail.class)
private String biography;
#JsonView(CharacterView.Summary.class)
private String name;
#JsonView(CharacterView.Summary.class)
private User player;
public CharacterModel(Long characterId, String name, String biography, User player) {
this.characterId = characterId;
this.name = name;
this.biography = biography;
this.player = player;
}
public CharacterModel(Long characterId) {
this.characterId = characterId;
}
...
// getters and setters
...
}
CharacterController.java
//package and imports
...
#RestController
#RequestMapping("/characters")
public class CharacterController {
#Autowired
private CharacterResourceAssembler characterResourceAssembler;
...
#JsonView(CharacterView.Summary.class)
#RequestMapping(value = "/resource-filtered", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public Resource<CharacterModel> getFilteredCharacterWithResource() {
CharacterModel model = new CharacterModel(1L, "TEST NAME", "TEST BIOGRAPHY", new User(1L, "Fred", "Flintstone"));
return characterResourceAssembler.toResource(model);
}
...
}
CharacterResourceAssembler.java
//package and imports
...
#Component
public class CharacterResourceAssembler implements ResourceAssembler<CharacterModel, Resource<CharacterModel>>{
#Override
public Resource<CharacterModel> toResource(CharacterModel user) {
Resource<CharacterModel> resource = new Resource<CharacterModel>(user);
resource.add(linkTo(CharacterController.class).withSelfRel());
return resource;
}
}

Categories