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.
Related
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);
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
}
I have a DynamoDB table in which I have columns. Two of them will always have the same datatype while one column's datatype will vary. How can I unmarshall/marshall it when using DynamoDB. Below is my DTO.
private Integer id;
private String name;
private Object value;
It is not allowing directly to map "value" field and throwing an exception.
Please help me in this regard.
Declare the field as:-
#CustomObjectFormat(separator = " ")
public Object getValue() {
return value;
}
Sample CustomObjectFormat code:-
The below implementation uses toString() to convert everything to String and persist as String data type in DynamoDB database.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#DynamoDBTypeConverted(converter=CustomObjectFormat.Converter.class)
public #interface CustomObjectFormat {
String separator() default " ";
public static class Converter implements DynamoDBTypeConverter<String, Object> {
private final String separator;
public Converter(final Class<Currency> targetType, final CustomObjectFormat annotation) {
this.separator = annotation.separator();
}
public Converter() {
this.separator = "|";
}
#Override
public String convert(final Object o) {
return o.toString();
}
#Override
public Object unconvert(final String o) {
return o;
}
}
}
Mapper to save:-
DynamoDBMapper will invoke the custom convert and unconvert accordingly for save and retrieval.
dynamoDBMapper.save(accounts);
DynamoDBTypeConverted JavaDoc
I have problems deserializing Enums that have multiple names for a value. Here is an example: Info is a Java class that inside has an enum with multiple names:
public class Info {
//...
private ContainerFormat format;
}
// ContainerFormat.java:
public enum ContainerFormat {
// ....
MP4("mp4", "mpeg4"),
UNKNOWN("null");
private String name;
private List<String> others;
ContainerFormat(String name) {
this.name = name;
}
/** The service does not always return the same String for output formats.
* This 'other' string fixes the deserialization issues caused by that.
*/
ContainerFormat(String name, String... others) {
this.name = name;
this.others = new ArrayList<String>();
for (String other : others) {
this.others.add(other);
}
}
#JsonValue
#Override
public String toString() {
return name;
}
public List<String> otherNames() {
return others;
}
#JsonCreator
public static ContainerFormat fromValue(String other) throws JsonMappingException {
for (ContainerFormat format : ContainerFormat.values()) {
if (format.toString().equalsIgnoreCase(other)) {
return format;
}
if (format.otherNames() != null && format.otherNames().contains(other)) {
return format;
}
}
return UNKNOWN;
}
}
The problem is when I deserialize something that contains "mpeg4" instead of mp4 I get this error:
com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not construct instance of com.foo.ContainerFormat from String value 'mpeg4': value not one of declared Enum instance names
at [Source: N/A; line: -1, column: -1] (through reference chain: com.foo.Info["format"])
at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:55)
at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:650)
at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:85)
at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:20)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:375)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:98)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:308)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:2769)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1478)
at com.fasterxml.jackson.databind.ObjectMapper.treeToValue(ObjectMapper.java:1811)
Any pointers on how to fix this?
TIA
I found a good solution based on Florin's answer:
the correct configuration with jackson 2.7.0-rc2 (and probably also before)
private ObjectMapper createObjectMapper() {
final ObjectMapper mapper = new ObjectMapper();
// enable toString method of enums to return the value to be mapped
mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
return mapper;
}
In your enum you just have to override the toString() method:
public enum EXAMPLE_TYPE {
START("start"),
MORE("more");
// the value which is used for matching
// the json node value with this enum
private final String value;
SectionType(final String type) {
value = type;
}
#Override
public String toString() {
return value;
}
}
You don't need any annotations or custom deserializers.
Get rid of String name and List<String> other and instead have just one field - List<String> names and serialize the single getter with #JsonValue
public enum ContainerFormat {
// ....
MP4("mp4", "mpeg4"),
UNKNOWN("null");
private List<String> names;
ContainerFormat(List<String> names) {
this.names = new ArrayList<String>(names);
}
#JsonValue
public List<String> getNames()
{
return this.names;
}
#JsonCreator
public static ContainerFormat getContainerFromValue(String value) throws JsonMappingException {
for (ContainerFormat format : ContainerFormat.values()) {
if(format.getValues().contains(value))
return format;
}
return UNKNOWN;
}
Alternatively, if you choose to keep your existing code, you could try annotating otherValues() with #JsonValue
Well, I found a workaround: one of these flags does the right thing and allows me to read that mpeg4 back in:
mapper.configure(org.codehaus.jackson.map.SerializationConfig.Feature.WRITE_NULL_PROPERTIES, false);
mapper.configure(org.codehaus.jackson.map.SerializationConfig.Feature.WRITE_ENUMS_USING_TO_STRING, true);
mapper.configure(org.codehaus.jackson.map.DeserializationConfig.Feature.READ_ENUMS_USING_TO_STRING, true);
mapper.setPropertyNamingStrategy(org.codehaus.jackson.map.PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
mapper.setSerializationInclusion(org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion.NON_EMPTY);
mapper.configure(org.codehaus.jackson.map.DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
I'm using Jackson + Spring in a REST API. One of the API calls has JSON that looks like this:
{
"status": "Suspended"
}
where the value of "status" is mapped to a Java enum, like so:
public enum FeatureStatus {
Activated(0),
Inactivated(1),
Suspended(2),
Deleted(3);
private FeatureStatus(int id) {
this.id = id;
}
private int id;
public int getId() {
return id;
}
public FeatureStatus valueOf(int id) {
switch(id) {
case 1: return Inactivated;
case 2: return Suspended;
case 3: return Deleted;
default: return Activated;
}
}
#JsonCreator
public static FeatureStatus fromValue(String status) {
if(status != null) {
for(FeatureStatus featureStatus : FeatureStatus.values()) {
if(featureStatus.toString().equals(status)) {
return featureStatus;
}
}
throw new IllegalArgumentException(status + " is an invalid value.");
}
throw new IllegalArgumentException("A value was not provided.");
}
}
However, this exception gets thrown: java.lang.IllegalArgumentException: { is an invalid value. It looks like it's not deserializing the JSON at all. My controller method has this definition:
public #ResponseBody void updateFeatureStatusById(#PathVariable long featureId, #RequestBody FeatureStatus updated) {
Any other controller automatically deserializes the JSON fine as expected using this format. How can I deserialize this JSON into my enum?
Put this class next to your controller:
final class FeatureStatusJsonObject {
public FeatureStatus status;
}
And then in the controller method use this:
#RequestBody FeatureStatusJsonObject updated
and get the real FeatureStatus by
updated.status
This makes the conversion from a JSON object to an Enum literal explicit in your code, and allows you to use the regular Enum <-> String (de)serialization elsewhere (if necessary).
On an unrelated note, this looks a bit funky:
#ResponseBody void
I would just make it void.
Is the exact JSON you pass an Object? Enums except either JSON String or number, not Object.
If you need to give an object, you should map it to something like:
public class EnumWrapper {
public FeatureStatus status;
}
and it'll work.