Duplicate fields in JSON Response - java

I'm using Spring boot Jackson dependency and lombok in my project, and in response i'm getting duplicate fields because of underscore
This is my model class:
#Getter
#Setter
#Accessors(chain = true)
#NoArgsConstructor
#ToString
public class TcinDpciMapDTO {
#JsonProperty(value = "tcin")
private String tcin;
#JsonProperty(value = "dpci")
private String dpci;
#JsonProperty(value = "is_primary_tcin_in_dpci_relation")
private boolean is_primaryTcin = true;
}
If i'm using underscore in is_primaryTcin field i'm getting below response with duplicate fields
{
"_primaryTcin": true,
"tcin": "12345",
"dpci": "12345",
"is_primary_tcin_in_dpci_relation": true
}
If i remove underscore from field isprimaryTcin then i'm getting correct response
{
"tcin": "12345",
"dpci": "12345",
"is_primary_tcin_in_dpci_relation": true
}
Is this because of underscore? but underscore is prefered to use in variable names right?

This is what your class look like after delomboking:
public class TcinDpciMapDTO {
#JsonProperty("tcin")
private String tcin;
#JsonProperty("dpci")
private String dpci;
#JsonProperty("is_primary_tcin_in_dpci_relation")
private boolean is_primaryTcin = true;
public String getTcin() {
return this.tcin;
}
public String getDpci() {
return this.dpci;
}
public boolean is_primaryTcin() {
return this.is_primaryTcin;
}
public TcinDpciMapDTO setTcin(String tcin) {
this.tcin = tcin;
return this;
}
public TcinDpciMapDTO setDpci(String dpci) {
this.dpci = dpci;
return this;
}
public TcinDpciMapDTO set_primaryTcin(boolean is_primaryTcin) {
this.is_primaryTcin = is_primaryTcin;
return this;
}
public TcinDpciMapDTO() {
}
public String toString() {
return "TcinDpciMapDTO(tcin=" + this.getTcin() + ", dpci=" + this.getDpci() + ", is_primaryTcin=" + this.is_primaryTcin() + ")";
}
}
If generated property name is not specified, Jackson generates it by stripping prefix is or get from the getter if using getter or by using Java field name if serializing field without using a getter. By default Jackson only uses getters during serialization. Because you put #JsonProperty on the fields, Jackson uses both fields and getters and checks if the field is already serialized by matching generated property name (this last part is my guess anyway) it does not recognize property generated from field is_primaryTcin and property generated from getter is_primaryTcin() as the same (one is internally named is_primaryTcin and the other _primaryTcin) - notice that if you rename is_primaryTcin to as_primaryTcin problem vanishes.

When you use is_primaryTcin that is not using underscore, it's using a mix of both.
You can fix it by using PropertyNamingStrategy.
If you do
...
#JsonNaming(PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy.class)
public class TcinDpciMapDTO {
private String tcin;
private String dpci;
private boolean isPrimaryTcinInDpciRelation = true;
}
The JSON output will be
{
"tcin": "12345",
"dpci": "12345",
"is_primary_tcin_in_dpci_relation": true
}

Related

Failed to convert boolean to enum using Jackson

I have following java class in a Springboot application
public enum Status {
DISABLED(false),
ENABLED(true);
private final boolean enabled;
Status(boolean value){
this.enabled = value;
}
public boolean value() {
return this.enabled;
}
/*
#JsonValue public boolean jsonValue() { return enabled; }
Error: Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `com.q.demo.model.Status` from Boolean value (token `JsonToken.VALUE_TRUE`);
*/
/*
public static Status forValue(#JsonProperty("enabled") Boolean status) {
if (status == null) {
return null;
}
if (Status.ENABLED.value() == status.booleanValue()) {
return Status.ENABLED;
} else {
return Status.DISABLED;
}
}
Error: Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Input mismatch reading Enum `com.q.demo.model.Status`: properties-based `#JsonCreator` ([method com.q.demo.model.Status#forValue(java.lang.Boolean)]) expects JSON Object (JsonToken.START_OBJECT), got JsonToken.VALUE_TRUE;
*/
}
public class User {
private Long userId;
private String userName;
private String role;
private String password;
private Status enabled;
//Getters and setters
}
I would like to serialize/deserialize json given below to the enum
{
"userName" : "usrer",
"role" : "role",
"password" : "psw",
"enabled" : true
}
I am not successful by using either #JsonProperty(which accepts String only) or #JsonValue (given in the code as commented line with error message) or #Jsconcreator (again code and error message given in the commented section. Can somebody give me a pointer? Jackson version is 2.13.0. Thank you.
All you need at this point is a way to tell Jackson to convert from and to your enum. You can combine #JsonValue and #JsonCreator inside your Status enum:
#JsonValue
public boolean value() {
return this.enabled;
}
#JsonCreator
public static Status of(boolean b) {
return b ? ENABLED : DISABLED;
}
#JsonValue (on the instance method) tells Jackson what value to use when serializing. The #JsonCreator annotation on that static method tells Jackson that the boolean taken from JSON can be used to resolve the corresponding enum value.

Unsafe Object binding Checkmarx

I am getting alert in Checkmarx scan saying Unsafe object binding in the saveAll() call.
The exact words in checkmarx are -
The columnConfigSet at src\main\java\com\ge\digital\oa\moa\controller\ConfigController.java in line 45 may unintentionally allow setting the value of saveAll in setColumnsConfig, in the object src\main\java\com\ge\digital\oa\moa\service\ConfigService.java at line 170.
Any idea how to rewrite the code , so that the checkmarx stops complaining.
My code:
#PutMapping("/columns")
#ResponseStatus(OK)
public void setColumnsConfig(#RequestBody(required=true) ColumnConfigSetDto columnConfigSet) {
service.setColumnsConfig(columnConfigSet);
}
public void setColumnsConfig(ColumnConfigSetDto columnConfigSet) {
String userId = columnConfigSet.getUserId();
String viewName = columnConfigSet.getViewName();
List<ColumnConfig> configs = new ArrayList<>();
for (ColumnConfigDto colConfig : columnConfigSet.getColumns()) {
// build a db config row only for the visibility property for now
ColumnConfigId confId = new ColumnConfigId();
confId.setUserId(userId);
confId.setViewName(viewName);
confId.setKey(colConfig.getKey());
confId.setProperty("visible");
ColumnConfig conf = new ColumnConfig();
conf.setColumnConfigId(confId);
conf.setValue(colConfig.getIsVisible() ? "true" : "false" );
configs.add(conf);
}
if (!configs.isEmpty()) {
configRepo.saveAll(configs);
}
}
Below are my DTO Objects which is used in this code :
#Getter
#Setter
public class ColumnConfigSetDto {
#JsonProperty("userId")
private String userId;
#JsonProperty("viewName")
private String viewName;
#JsonProperty("columns")
private List<ColumnConfigDto> columns;
}
Below are my DTO code which is used in this
#Getter
#Setter
public class ColumnConfigDto {
#JsonProperty("key")
private String key;
#JsonProperty("label")
private String label;
#JsonProperty("isVisible")
private Boolean isVisible;
#JsonProperty("position")
private Integer position;
#JsonProperty("isSortable")
private Boolean isSortable;
#JsonProperty("isHideable")
private Boolean isHideable;
}
Here is my solution for Unsafe object binding reported by cherkmarx in Java.
It's not a graceful approach and only fix this vulnerability.
Remove all setter methods for boxed fields in each requestbody bean.
Since #JsonProperty could support deserialization capbility, no need to add setter manually.
If you need setter for request body bean indeed, you can use reflaction way instead.
FieldUtils.writeField(columnConfigDto , "isVisible", true, true);
public class ColumnConfigDto {
// Ensure #JsonProperty existed on each field
#JsonProperty("key")
private String key;
#JsonProperty("isVisible")
private Boolean isVisible;
#JsonProperty("list")
private List list;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Boolean getVisible() {
return isVisible;
}
// Remove boxed type field
// public void setVisible(Boolean visible) {
// isVisible = visible;
// }
public List getList() {
return list;
}
// Remove boxed type field
// public void setList(List list) {
// this.list = list;
// }
}
this issue occurs due to #RequestBoby as per spring documentation but there is no issue for #RequestParam. if we bind request body to object without #RequestBody, this issue is not occurred.
HttpServletRequest request;
mapper.readValue(request.getInputStream(), Product.class);
The error is also thrown if data is set to an object annotated with #RequestBody.
requestBodyVariable.setAdditionalValue(valueFromRequestParamOrPathVariable);
// This setter call should not be used
Instead, use a user-defined variable for storing the value from request param, header or path variable in its place:
service.callServiceMethod(requestBodyVariable, valueFromRequestParamOrPathVariable);

Improved way to convert nested JSON object's boolean values into a Map with Jackson

I have the following JSON object
{
"donor": "Y",
"bloodType": null,
"eligibility": {
"categoryEligible": false,
"suspensionEligible": false,
"paidFinesEligible": false,
"pointSystemEligible": false,
"failedDocuments": [
{
"type": "SOMETHING",
"reason": "SOMETHING_ELSE"
}
],
"eligible": false,
}
}
I'm using Jackson to convert it into my domain object. Here are the fields I'm using:
private String donor;
#JsonProperty("eligibility")
private Eligibility eligibility;
The Eligibility class contains all these fields, I want to instead of having individual fields for all the boolean values, to have a single Map< String, Bolean > where String is the property name and boolean is the value.
#JsonProperty("failedDocuments")
private List<FailedDocumentsItem> failedDocuments;
#JsonProperty("eligible")
private boolean eligible;
#JsonProperty("donor")
private boolean donor;
Add an #JsonAnySetter field (Jackson 2.8+) or method:
Marker annotation that can be used to define a logical "any setter" mutator -- either using non-static two-argument method (first argument name of property, second value to set) or a field (of type Map or POJO) - to be used as a "fallback" handler for all otherwise unrecognized properties found from JSON content.
Example using public fields for brevity.
public class Test {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Root root = mapper.readValue(new File("test.json"), Root.class);
System.out.println("donor = " + root.donor);
System.out.println("flags = " + root.eligibility.flags);
System.out.println("failedDocuments = " + root.eligibility.failedDocuments);
}
}
class Root {
public Boolean realId;
public String donor;
public Boolean bloodType;
public Boolean selectiveServiceCandidate;
public Eligibility eligibility;
}
class Eligibility {
#JsonAnySetter
public Map<String, Boolean> flags = new HashMap<>();
public List<FailedDocument> failedDocuments;
}
class FailedDocument {
public String type;
public String reason;
#Override
public String toString() {
return "FailedDocument[type=" + this.type + ", reason=" + this.reason + "]";
}
}
Output
donor = Y
flags = {paidFinesEligible=false, hasRealId=false, suspensionEligible=false, acaaEligible=false, eligibleIgnoreRenewalDate=false, eligibleDocuments=false, cardStatusEligible=false, expirationDateEligible=false, eligible=false, citizenEligible=false, pointSystemEligible=false, ageEligible=false, gravamenesEligible=false, categoryEligible=false, eligibleMedical=false}
failedDocuments = [FailedDocument[type=CERTIFICATE_CITIZENSHIP, reason=MISSING]]

Jackson objectMapper mapping different json properties to same pojo

I have a very simple json which I am trying to map to an object.
JSON :
[
{
"cust_lpid": "0119b9f7f99ad2161de7b0b",
"cust_uid": "soumavtestflow"
}
]
My Mapper Class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class CustomerSegmentRequest {
#JsonProperty("LPID")
String cust_lpid;
#JsonProperty("UserId")
String cust_uid;
public String getCust_lpid() {
return cust_lpid;
}
public void setCust_lpid(String cust_lpid) {
this.cust_lpid = cust_lpid;
}
public String getCust_uid() {
return cust_uid;
}
public void setCust_uid(String cust_uid) {
this.cust_uid = cust_uid;
}
}
When I do a
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
LPIDCustIDMapper[] custSegResp = objectMapper.readValue(responseBody,CustomerSegmentRequest [].class);
I don't get any values populated in custSegResp.
However when i remove the #JsonProperty it works.
I need the json property name to map an incoming request and hence don't wanna create a separate mapping class.
Is there a way to achieve the same?
use #JsonAlias
#JsonIgnoreProperties(ignoreUnknown = true)
public class CustomerSegmentRequest {
#JsonAlias({"cust_lpid", "LPID" })
String cust_lpid;
#JsonAlias({"cust_uid", "UserId" })
String cust_uid;
public String getCust_lpid() {
return cust_lpid;
}
public void setCust_lpid(String cust_lpid) {
this.cust_lpid = cust_lpid;
}
public String getCust_uid() {
return cust_uid;
}
public void setCust_uid(String cust_uid) {
this.cust_uid = cust_uid;
}
}
#JsonProperty("keyName") is to specify what is the key is the JSON which maps to this field.
The reason it works without it is, without it Jackson tries to first match via getters/setters after removing the get/set prefix keys and normalizing the case (getAbCd -> abCd), which in your case gives the keys as in the JSON.
You need to modify your #JsonProperty("LPID") to #JsonProperty("cust_lpid") or if you need to map to multiple keys use #JsonAlias({"cust_lpid", "LPID" })

Polymorphic String Deserialization

I am using java w/ Jackson and would like to use a string prefix as my type for deserialization and the java pojo for generating that prefix at serialization.
class A implements TopLevel {
String id;
public String getPrefix() {
return "aPrefix"
}
}
class B implements TopLevel {
String id;
public String getPrefix() {
return "bPrefix"
}
}
interface TopLevel {
String getPrefix()
}
//This should create an instance of A w/ Id = "423412421421412RandomId"
mapper.readValue("aPrefix.423412421421412RandomId", TopLevel.class)
//This should create an instance of A w/ Id = "OtherRandomId"
B b = mapper.readValue("bPrefix.OtherRandomId", TopLevel.class)
//This should create string "bPrefix.OtherRandomId"
mapper.writeValue(b)
Ideally I would like to be able to define the following, and use #JsonSubTypes or the most standard Jackson way possible to be able to create these value type strings.

Categories

Resources