Jackson seems to have a problem when the second letter of a field name is capitalized.
Take a map of values:
aaBoolean, true // works
aBoolean, false // fails
anInt, 0 // works
aString, "a" // fails
I used Jackson's ObjectMapper.convertValue(map) to create a Java object. Here's a snippet of Java code:
private boolean aaBoolean; // GOOD
public boolean getAaBoolean() { return aaBoolean; }
public void setAaBoolean(boolean value) { this.aaBoolean=value;}
private boolean aBoolean; // FAILS!!! Jackson "Unrecognized field"
public boolean getABoolean() { return aBoolean; }
public void setABoolean(boolean value) { this.aBoolean=value;}
I get an error message with all 18 fields. Note the camel case fails when the capital is the second letter:
Unrecognized field "aBoolean" (class Test), not marked as ignorable
(18 known properties: "anInt", "anullableBoolean", "aboolean", "aaBoolean",
"lastModifiedDate", "adate", "anullableDate", "astring", "anullableString",
"createdDate", "anullableFloat", "id", "along", "anullableLong", "createdBy",
"anullableInt", "lastModifiedBy", "afloat"])
If I change aBoolean to aaBoolean, that passes and Jackson fails on aString.
Per other Stack Overflow posts, I've verified that the field name and getter/setter match (aBoolean is getABoolean() and aaBoolean is getAaBoolean()).
If it matters, here's how the ObjectMapper was created:
ObjectMapper mapper = new ObjectMapper();
mapper.setTimeZone(TimeZone.getTimeZone("CST"));
I can post the full code but I think the above is enough.
I can modify my variable names to get around this, but now I'm curious - is this a bug or am I missing something about how Jackson handles name conversion?
It looks like the default Jackson behavior through v2.9 is to lowercase any leading upper case getter/setter methods. So "getURLtoServer" becomes "urltoServer".
Jackson source code executing this here:
https://github.com/FasterXML/jackson-databind/blob/2.9/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java#L246
However, the JavaBean spec says to not change any casing if the first two characters are uppercase. So the JavaBean of "getURLtoServer" would be "URLtoServer".
Jackson introduced MapperFeature.USE_STD_BEAN_NAMING as an option to enforce this part of the JavaBean spec. However, it looks like that's being removed in v3.x when it becomes the default behavior:
https://github.com/FasterXML/jackson-databind/issues/1772
So for your case, it looks like you can either have the JSON string "aboolean" without using USE_STD_BEAN_NAMING, or else you can have the JSON string "ABoolean" with using USE_STD_BEAN_NAMING.
The other option is to manually specify what you want:
#JsonProperty("aBoolean")
public boolean getABoolean() { return aBoolean; }
Related
I have a Spring Boot project in Kotlin which uses a custom locale interceptor to know from which .properties file it load messages.
This works pretty fine. Also I have a custom annotation which is simple and has a default message value, as follow:
#Target(AnnotationTarget.Field)
#Constraint(validatedBy = [MyCustomValidator::class])
annotation class MyAnnotation(
val message: String = "{javax.validation.constraints.MyAnnotation.message}",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)
class MyCustomValidator : ConstraintValidator<MyAnnotation, String> {
override fun isValid(value: String, context: ConstraintValidatorContext) {
return true //Just to make it easy
}
}
The locale properties files contains the key MyAnnotation.value=This field is required and shows as the exception message.
The problem is when I want to add more validations and so, custom messages according to each condition. I read that I should disable the default constraint validator and add the messages, but it is not working.
For example, if I want to create a key at locale file as MyAnnotation.conditionOne, it still prints the message from MyAnnotation.value.
//The only thing that changes in MyAnnotation is that message: String = ""
//Assuming that ConditionOne and ConditionTwo has a isValid static method
class MyCustomValidator : ConstraintValidator<MyAnnotation, String> {
override fun isValid(value: String, context: ConstraintValidatorContext): Boolean {
context.disableDefaultConstraintViolation()
return if (!ConditionOne.isValid(value)) {
context
.buildConstraintViolationWithTemplate("{javax.validation.constraints.MyAnnotation.conditionOne}")
.addConstraintViolation()
false
}
else if (!ConditonTwo.isValid(value)) {
context
.buildConstraintViolationWithTemplate("{javax.validation.constraints.MyAnnotation.message}")
.addConstraintViolation()
false
}
else
true
}
}
Is this the right way to set the message?
As I can see on the example above, you added two constraint violation message but in the different if cases. For a getting several checks and violation messages from one validator you should have not placed return after each if cases, instead of this you can create a local boolean variable and set its value from each of if statement cases, and after all make return that variable.
Pay attention to a little thing: it is important for your validator to set a temporary boolean variable correctly, because if once your if was set into false that means ultimate return value should be false. Cuz there is a principle anyMatch(false) or allMatch(true)
I am using Retrofit in order to get a JSON document. Problem is, all booleans are always false.
The response looks like this:
{
"gender":[0,1],
"age":[20,30],
"likesLeaveHome":false,
"likesSport":false,
"likesCulture":false,
"likesTraveling":false
...
}
I am calling the retrofit method with
onResponse(Call<SearchProfile> call, Response<SearchProfile> response)
And the class of SearchProfile which the response should be parsed to looks like that:
public class SearchProfile {
public ArrayList<Integer> gender = new ArrayList<>(); // works fine
public ArrayList<Integer> age = new ArrayList<>(); // works fine
...
public Boolean likesLeaveHome = true; // always false
#SerializedName("likesSport")
public boolean likesSport = true; // always false
#SerializedName("likesCulture")
public Boolean likesCulture; // always false
#SerializedName("likesTraveling")
public Boolean mLikesTraveling; // always false
public boolean isLikesTraveling() {
return mLikesTraveling;
}
public void setLikesTraveling(boolean likesTraveling) {
mLikesTraveling = likesTraveling;
}
}
As you can see, it is a simple pojo class. Lists like "gender" and "age" work perfectly fine. Still, the booleans can't be set. (This is especially strange since sending this object via Retrofit sends exactly this document so GSON surely knows booleans).
As shown in the snipped, I also tried other methods like giving the Boolean wrapper class as type instead of boolean.
I also used a #SerializeName annotation or getter and setts methods etc.
Still all booleans are always false. Even if I declare them default as true (so GSON always seems to overwrite this value with false).
Hope someone has a good idea!
The JSON you are parsing in your example has all the values as false. When parsing, GSON will use reflection to overwrite field values for a class, meaning that whatever value is parsed from JSON will be the value of the field, regardless of how it is initialized in the class.
Here is a sample response that will cause the fields to be set to true. Again, it's just up to whatever JSON you are parsing.
{
"gender":[0,1],
"age":[20,30],
"likesLeaveHome":true,
"likesSport":true,
"likesCulture":true,
"likesTraveling":true
...
}
Null value in second column (incoming csv file):->
input CSV: 10512,,
10513,12345,
impex:
INSERT_UPDATE Product;code[unique=true];vendors(code)[translator=ca.batch.converter.StiboSetDefaultVendorIfNullTranslator];...
code:
Extending de.hybris.platform.impex.jalo.translators.AbstractValueTranslator;
private final String defaultVendorCode = "000000";
#Override
public Object importValue(String valueExpr, final Item item)
throws JaloInvalidParameterException {
if (valueExpr == null || StringUtils.isEmpty(valueExpr)) {
LOG.debug("Current attribute value is null so inserting "
+ defaultVendorCode);
valueExpr = defaultVendorCode;
}
return valueExpr;
}
getting the same below error here also for the 12345 but final impex conveterd row has the number (impex row -> 10153;12345)
due to Argument mismatch trying to set value '000000' for attribute de.hybris.platform.jalo.product.Product.vendors (got java.lang.String, expected de.h
ybris.platform.jalo.product.Product).,
(impex row -> 10153;;)
You should try this:[allownull=true] attribute modifier;
Your impex should look like this:
INSERT_UPDATE Product;code[unique=true];vendors(code)[allownull=true]
Only Import
true / false
Default: false
If set to true, this modifier explicitly allows null values for the column values. If there is no business code that blocks null values, this modifier even allows null values in mandatory attributes, such as the catalogVersion attribute of the Media type, for example.
Example:
[allownull=true]
Tip
In the Service Layer mode, import may fail if allownull is set. Since hybris Commerce Suite version 5.1.1, import will switch dynamically to the legacy mode if it encounters this parameter. After processing a given line, the import will switch back to the SL mode.
I think the error message is quite clear on this:
(got java.lang.String, expected de.h ybris.platform.jalo.product.Product).,
For the translator you'd have to lookup the actual default vendor object instead of returning the default vendor code.
I think the easiest solution would be if you used a Decorator instead that then returns the code values of your "vendors" attribute.
You can find detailed instructions here:
https://wiki.hybris.com/display/release5/ImpEx+API#ImpExAPI-WritingOwnCellDecorator
but basically something like this:
public class MyDecorator implements CSVCellDecorator
{
public String decorate( int position, Map<Integer, String> srcLine )
{
// here add your custom logic to check and if applies return your default vendor code, otherwise return the given input value
//String parsedValue=srcLine.get(position);
//return parsedValue+"modified"; // some decoration stuff
}
}
Hope that helps a bit :)
I currently have the following class
public class Setting<T> {
private T value;
private T defaultValue;
/* getters and setters */
}
The trouble I'm having is deserializing from the Json, for example, having a Setting will work fine if the Json is
{ \"value\": true, \"defaultValue\": false }
.. but, it will also accept
{ \"value\": \"true\", \"defaultValue\": \"false\" }
Even tho the field is of Setting. From what I understand, Java strips away all the generic info, so jackson cannot look it up at runtime. I have tried the following
Field currentSettingField = currentSettingsObject.getClass().getDeclaredField("mySetting");
if (currentSettingField != null) {
JavaType settingType = mapper.getTypeFactory().constructType(settingField.getGenericType(), Setting.class);
Setting setting = objectMapper.readValue(currentSettingNode.toString(), settingType);
}
Which has been somewhat successful, however I can still do things like converting a String or Integer value from Json to a type of Setting. Am I doing something wrong here, or would I be better just have a simple marker interface, then derive each type of setting I want from it?
From what I see from Jackson's code, it tries it's best to guess the value for the boolean and matches strings:
// And finally, let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
String text = jp.getText().trim();
if ("true".equals(text)) {
return Boolean.TRUE;
}
if ("false".equals(text)) {
return Boolean.FALSE;
}
if (text.length() == 0) {
return (Boolean) getEmptyValue();
}
throw ctxt.weirdStringException(text, _valueClass, "only \"true\" or \"false\" recognized");
}
So if you change you generic type to Integer or provide an incorrect input i.e. 'tru', it won't able to match it.
"Java strips away all the generic info"
AFAIK, Java can do this, but it doesn't remove type attributes information from classes. It doesn't use them however during runtime. This make possible for libraries like Jackson are able to use it.
I'm using Fasterxml Jackson 2.2.2
I have a simple pojo with a boolean (primitive) attribute. When the default BeanSerializer and BeanPropertyWritter try to serialize it, this attribute is skipped when its value is false.
I want:
{"id":1, "enabled":false}
What I get is:
{"id":1}
The code in BeanPropertyWritter is:
// and then see if we must suppress certain values (default, empty)
if (_suppressableValue != null) {
if (MARKER_FOR_EMPTY == _suppressableValue) {
if (ser.isEmpty(value)) {
return;
}
} else if (_suppressableValue.equals(value)) {
return;
}
}
I've debugged it and found that BeanPropertyWritter._suppressableValue equals Boolean(false), so when a false boolean arrives to this block, it just returns and no output is returned.
What are my options? Can I configure the attribute's writter to un-set its _suppressableValue? What would be the easiest and simpler solution?
As was suggested, your ObjectMapper settings are probably non-default, and specify inclusion strategy of NON_DEFAULT.
But you can add #JsonInclude to override this either on your POJO class, or even for boolean property itself: make sure to use Inclusion.ALWAYS.