Deserialise Json to class with Generic fields - java

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.

Related

Return dynamic or union type from lambda in Java

I'm returning to Java after having been away for a very, very long time, and I'm trying to do in Java the sort of thing that I've often done in Javascript or Typescript. But I can't figure it out.
I'm trying to create a mapping between two different systems to easily map types and copy values from one system to the other. The systems are Neo4j and Excel, although my question doesn't really have anything to do with either of those. Basically the idea is that users can export data from Neo4j to an Excel sheet, edit values there, and then import it again. (I know! It's a terrible idea! But they really want it and we can blame them if they mess up.)
Still, at the very least I'd like them to not change the types of data in neo4j properties, so I'm building a fairly strict mapping:
public enum Neo4jExcelDataTypeMapping {
STRING("String", cell -> cell.getStringCellValue()),
INTEGER("Integer", cell -> cell.getNumericCellValue()),
FLOAT("Float", cell -> cell.getNumericCellValue()),
BOOLEAN("Boolean", cell -> cell.getBooleanCellValue()),
STRING_ARRAY("String[]",
cell -> Neo4jExcelDataTypeMapping.deserializeArrayToString(cell.getStringCellValue()),
value -> serializeArray((Object[]) value)
),
DATE("Date",
cell -> LocalDate.parse(cell.getStringCellValue()),
value -> ((LocalDate)value).format(DateTimeFormatter.ISO_LOCAL_DATE)),
);
interface ExcelCellReader {
Object readValue(Cell cell);
}
interface ExcelCellWriter {
Object writeValue(Object value);
}
public final String className;
public final ExcelCellReader excelCellReader;
public final ExcelCellWriter excelCellWriter;
private Neo4jExcelDataTypeMapping(String className, ExcelCellReader excelCellReader) {
this.className = className;
this.excelCellReader = excelCellReader;
this.excelCellWriter = value -> value;
}
private Neo4jExcelDataTypeMapping(String className, ExcelCellReader excelCellReader, ExcelCellWriter excelCellWriter) {
this.className = className;
this.excelCellReader = excelCellReader;
this.excelCellWriter = excelCellWriter;
}
public static Neo4jExcelDataTypeMapping fromNeo4jType(String type) {
for (Neo4jExcelDataTypeMapping instance : Neo4jExcelDataTypeMapping.values()) {
if (instance.className.equals(type)) {
return instance;
}
}
return null;
}
}
(I'm leaving out the serialization/deserialization of arrays in a readable manner, because that's a whole separate issue.)
This code seems fine. The IDE doesn't complain about it at least. But then I try to use it in a unit test:
#Test
public void testStringMapping() {
String testString = "Test String";
VirtualNode node = new VirtualNode(1);
node.setProperty("test", testString);
Cell cell = row.createCell(0);
String neo4jType = node.getProperty("test").getClass().getSimpleName();
Neo4jExcelDataTypeMapping mapping = Neo4jExcelDataTypeMapping.fromNeo4jType(neo4jType);
cell.setCellValue(mapping.excelCellWriter.writeValue(node.getProperty("test")));
}
Here, the problem is that cell.setCellValue() expects a specific type. It's heavily overloaded, so it can accept String, Integer, Float, various Dates, and more. But not Object. Now all the objects I'm returning from my lambda will be one of those types, but the lambda itself says it's returning an Object, and Java doesn't like that.
In TypeScript, I might have it return String|Integer|Float|Date, but you can't do that in Java. Is it possible to do this with generics somehow?
I don't want to explicitly cast in cell.setCellValue((String)mapping.excelCellWriter.writeValue(node.getProperty("test"))); because this is just going to be a single line that should work for all data types. I wrote the enum exactly to handle this, but it can't seem to handle this one final cast. I suspect some generic type magic could do it, but I never really mastered that aspect of Java.
(Also: I'm not a fan of the excelWriter.writeValue(). Is there a way to make excelWriter() both a property of the enum and the function that's called? Or is that too JavaScript of me?)

Inconsistent Jackson "Unrecognized field" due to capitalization

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; }

Check if a string is parsable as another Java type

I'm trying to find a way to check if a string is parsable as a specific type.
My use case is :
I've got a dynamic html form created from a map of field (name and type)
In my controller I get back the form values from the http request as strings
I'd like to check if the retrieved string is parsable as the wanted type, in order to display an error message in the form if this is not possible.
Does someone know a way to check if the parsing is possible without testing each type one by one ? (Double, Integer, Date, BigDecimal, etc.)
I'm looking for something like that in Java or in a third party library :
myString.isParsableAs(Class<?> wantedType)
Thanks for the help !
Make a map from Class to Predicate, and use that to obtain a "tester object" for your class. Here is an example:
static Map<Class<?>,Predicate<String>> canParse = new HashMap<>();
static {
canParse.put(Integer.TYPE, s -> {try {Integer.parseInt(s); return true;} catch(Exception e) {return false;}});
canParse.put(Long.TYPE, s -> {try {Long.parseLong(s); return true;} catch(Exception e) {return false;}});
};
You can now retrieve a predicate for the class, and test your string, like this:
if (canParse.get(Long.TYPE).test("1234567890123")) {
System.out.println("Can parse 1234567890123");
} else {
System.out.println("Cannot parse 1234567890123");
}
You wouldn't have to go though the entire list of testers; the check will happen only for the type that you want to test.
Demo.

Equivalent to "Abc.class.getName()" for fields

In Java you can write:
System.out.println(Abc.class.getName());
It will always print the correct value, even if the class Abc is moved or renamed.
What is the closest you can get to that for field names?
In other words, what can I code that will always give me the "current" name of a field in a class, even if that field is renamed. Ideally, it would also fail to compile if the field is removed altogether. Or at least fail as soon as the class is accessed, during static initialisation.
I want this to simplify a "change tracking" system. It's not quite like "bean properties", because those names are NOT visible outside the class itself.
AFAIK, there is no "native" way to do this, but I'm hoping there might be some trick with annotations and/or reflection that does the job.
I'll write what I'm doing now (minimally simplified):
private static final String IS_SWAPPABLE = "isSwappable";
// ...
private boolean isSwappable;
// ...
public boolean isSwappable() {
if ((clientChanges != null) &&
clientChanges.containsKey(IS_SWAPPABLE)) {
return (Boolean) clientChanges.get(IS_SWAPPABLE);
}
return isSwappable;
}
public boolean setSwappable(final boolean newSwappable) {
if (isSwappable() != newSwappable) {
isSwappable = newSwappable;
onFieldChange(IS_SWAPPABLE, newSwappable);
return true;
}
return false;
}
What I would like is some "magic" that sets the value of IS_SWAPPABLE to "isSwappable" such that if isSwappable is renamed, then IS_SWAPPABLE will be updated appropriately.
OTOH, if there was a syntax like Abc.isSwappable (or Abc#isSwappable) or whatever, I would spare myself the constant, and just write that directly.
What I can do atm is (once) go over the constants (by using some clear naming convention), and make sure for each of them there is an instance field with the same name. But it doesn't really guaranties that IS_SWAPPABLE is used where isSwappable is used.
It looks very simple.
for (Field field : getClass().getFields()) {
System.out.println(field.getName());
}
Then you can mark fields with your own annotations and then read their values like this
for (Field field : getClass().getFields()) {
System.out.println(field.getName());
MyAnnotation annotation = field.getAnnotation( MyAnnotation.class );
System.out.println(annotation.value());
}

Fasterxml Jackson primitive boolean serialization

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.

Categories