Collecting unknown properties with Jackson - java

I'm using Jackson for creating Java objects from JSON. Let's suppose I have a JSON string like this:
{"a":"a", "b":"b", "c":"c"}
And a pojo like this:
#JsonIgnoreProperties(ignoreUnknown = true)
public class A {
private String a;
private String b;
// ...
}
So c is clearly an unknown property - and it will be left out. I was wondering, is there any way I can log that c was unknown and ignored?

I don't know of any built-in tool that does this. You can write your own with #JsonAnySetter
Marker annotation that can be used to define a non-static,
two-argument method (first argument name of property, second value to
set), to be used as a "fallback" handler for all otherwise
unrecognized properties found from JSON content.
Use it like
#JsonAnySetter
public void ignored(String name, Object value) {
// can ignore the 'value' if you only care for the name (though you still need the second parameter)
System.out.println(name + " : " + value);
}
within the class you're deserializing to, eg. your A class.

#JsonAnySetter when used on a Map field would catch all unmapped properties. When paired with #JsonAnyGetter would work for serialization and deserialization:
#JsonAnyGetter
#JsonAnySetter
private Map<String, Object> dynamicValues = new LinkedHashMap<>();

Related

Spring - How to pass different types at once in JSON using PATCH

i have a PATCH mapping method that currently accepts a Map<String, Object> as #ResponseBody, because i've seen ReflectionUtils as one of ways to implement the PatchMapping, but when I want to pass for example String, Integer and Long, then JSON doesn't distinguish which one are which one. I would like to be able to pass EntityID which is Long, some Value which is Integer and some Strings at once.
This is my current Controller method
#PatchMapping("/grades/{id}")
public GradeEto partialUpdate(#PathVariable("id") final Long id, #RequestBody Map<String, Object> updateInfo) {
return gradeService.partialUpdate(id, updateInfo);
}
This is main part of the ReflectionUtils update
#Override
public GradeEto partialUpdate(Long id, Map<String, Object> updateInfo) {
Grade grade = gradeRepository.findById(id).get();
GradeEto gradeeto = GradeMapper.mapToETO(grade);
updateInfo.forEach((key, value) -> {
Field field = ReflectionUtils.findField(GradeEto.class, key);
field.setAccessible(true);
ReflectionUtils.setField(field, gradeeto, value);
});
And i want to be able to pass for example
{
"value": 2, <-- Integer
"comment": "Test Idea", <-- String
"subjectEntityId": 2 <-- Long
}
But it gives me IllegalArgumentException currently
How should i do it? And if im doing it the wrong way then how should I do it?
You can make use of the functionality offered by Jackson to perform the partial update.
For that, instead of reading the data to patch as a Map you specify in your Controller the type of the request body as ObjectNode:
#PatchMapping("/grades/{id}")
public GradeEto partialUpdate(#PathVariable("id") final Long id,
#RequestBody ObjectNode updateInfo) {
return gradeService.partialUpdate(id, updateInfo);
}
Then inside the Service to operate with ObjectNodes you need to turn GradeEto instance into a node tree using ObjectMapper.valueToTree() (assuming that Service class is a managed Bean you can inject ObjectMapper into it instead of instantiating it locally).
Method ObjectNode.fields() can be used to iterate over the fields and corresponding values of updateInfo. Methods isNull() and isEmpty(), which ObjectNode derives from its super types, would be handy to check if a particular property needs to be updated.
Then, finally, you would need to parse the ObjectNode back into GradeEto type.
So the code in the Service might look like this:
#Service
public class GradeService {
private ObjectMapper mapper;
public GradeEto partialUpdate(Long id, ObjectNode updateInfo) throws IOException {
Grade grade = gradeRepository.findById(id).get();
GradeEto gradeeto = GradeMapper.mapToETO(grade);
ObjectNode gradeEtoTree = mapper.valueToTree(gradeeto);
updateInfo.fields().forEachRemaining(e -> {
if (!e.getValue().isNull() && !e.getValue().isEmpty())
gradeEtoTree.replace(e.getKey(), e.getValue());
});
return mapper
.readerFor(GradeEto.class) // creates an ObjectReader
.readValue(gradeEtoTree);
}
}
Note: it's worth to draw the reader's attention to the fact we also need to update the information in the Database. This logic is missing in both the Question and this Answer, since it's not relevant to the narrow technical problem of extracting the data that should be patched from JSON, but we definitely need to save the patched object (for instance a method that produces patched object can be wrapped with another method responsible for retrieving/updating the data in the database).
I do believe org.springframework.cglib.beans.BeanMap can solve your problem in elegant way:
public static void main(String[] args) throws Exception {
String json = "{\n" +
" \"value\": 2,\n" +
" \"comment\": \"Test Idea\",\n" +
" \"subjectEntityId\": 20\n" +
"}";
Grade grade = new Grade();
BeanMap map = BeanMap.create(grade);
map.putAll(new ObjectMapper().readValue(json, Map.class));
assertThat(grade.value)
.isEqualTo(2);
assertThat(grade.subjectEntityId)
.isEqualTo(20);
assertThat(grade.comment)
.isEqualTo("Test Idea");
}
#Data
static class Grade {
int value;
String comment;
long subjectEntityId;
}

Convert annotation string to enum value with Jackson

I have an enum with #JsonProperty annotated:
public enum Type {
#JsonProperty("Files")
File,
#JsonProperty("Folders")
Folder,
}
I know I can deserialize a JSON string ({"fieldName":"Files"}) to get an object. But is there any way to convert the string annotated in #JsonProperty to enum value with Jackson like:
String s = "Files"
Type t = jackson.valueOf(s); // Type.File
Or can I achieve this:
Type t = Type.File;
String s = jackson.toString(t); // "Files"
I believe that a private String value can resolve this, but the code would have duplicated constants (too many "Files" and "Folders"). I wonder if Jackson or Gson has solution to achieve this.
It should just work with help of ObjectMapper
Type t = new ObjectMapper().readValue("\"Files\"", Type.class);
System.out.println(Type.File.equals(t)); //"true"
Please note that the String needs to be a valid JSON string, so it must contain the double quotes. The string content cannot be Files, but rather has to be "Files"
The other direction:
Type t = Type.File;
new ObjectMapper().writeValue(System.out, t); // "Files"
If I am understanding you correctly, then perhaps this is what you are looking for:
String annotationValueAsSTring = Type.class.getField(Type.File.name())
.getAnnotation(JsonProperty.class).value();
-- Edited --
To retrieve the Enum value from the #JsonProperty String value, then you will need to create a helper method:
public static <T extends Enum<T>> Optional<T> getEnumValueFromJsonProperty(Class<T> enumClass,
String jsonPropertyValue) {
Field[] fields = enumClass.getFields();
return Arrays.stream(fields).filter(field -> field.getAnnotation(JsonProperty.class).value().equals(jsonPropertyValue)).map(field -> Enum.valueOf(enumClass, field.getName())).findFirst();
}

How to create list of Maps from List of Object in java without having getKey method?

How to create a list of maps, where each key name is inferred from name of the class attribute, and value is to be put by getter method
I am having following class in java
class DTA {
private String id;
private String age;
#Override
public String toString() {
return "DTA{" +
"id='" + id + '\'' +
", age='" + age + '\'' +
'}';
}
public DTA(String id, String age) {
this.id = id;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
I am having a list of objects of type DTA
List<DTA> listA = new ArrayList<>();
listA.add(new DTA("A", "15"));
listA.add(new DTA("B", "25"));
I want to create an ordered list of maps (somewhat like scala) which has following content.
List<? extends Map<String, String>>
List(Map("id"->"A", "age"->"15"), Map("id"->"B", "age"->"25"))
Without "dynamics", the straight forward thing might look like:
List<Map<String, String>> x = listA
.stream()
.map(this::toMap)
.collect(Collectors.toList());
with a local helper, such as:
private Map<String, String> toMap(DTA dta) {
Map<String, String> rv = new HashMap<>();
rv.put("id", dta.getId());
rv.put("age", dta.getAge());
return rv;
}
In order to be fully dynamic here, you would have to use reflection to query the field names. You can find examples how to do that here.
But I strongly suggest to not do that: reflection should always be your last resort. The notion of DTA suggests that you have that object data coming from some "service" anyway. If so, why first serialize into a specific DTA class, to then "flatten" that information into some generic Map structure?!
Meaning: when that service gives you objects that are serialized as, say JSON, or XML ... then it would be much better to simply use a library like gson or jackson to directly deserialize that data into such generic "flat" Map-based objects. Jackson for example has a JsonNode class. When you deserialize into such objects, you get that mapping of field names for free! See here more example code.
The point is: identifying fields using reflection is possible. But reflection code is always tedious, and error prone. If possible, stay away from doing that yourself.
Basically, the tool used to "look into" the contents of classes in Java is called reflection. For example, if your object is a POJO (Plain Old Java Object), you can iterate over all fields in the class in the following way:
DTA obj; // some object, I assume it's initialized
Field[] fields = DTA.class.getDeclaredFields();
Map<String, Object> valuesMap = new HashMap<>();
for (field : fields) {
boolean wasAccessible = field.isAccessible(); // check if class has access control disabled
field.setAccessible(true); // disable access control (private/protected) to extract value
valuesMap.put(field.getName(), field.get(obj));
field.setAccessible(wasAccessible); // return flag to initial value
}
However, accessing values via reflection this way is notoriously hacky. Unless you have good reasons to do it yourself, try using a framework that automates tasks like that rather than writing code like this from scratch.
Also, reflection is slow. Accessing Field entities like that for every single object is suboptimal, if you ever want to really write code like this, you should cache the Field objects in a Map<String, Field> and only do the setAccessible override and the Field retrieval once for every collection of DTA objects.

Include enum type to serialize and deserialize with jackson

I have a simple enum I'd like to serialize and deserialize. The class looks like this:
public enum TipusViatge {
OCI,
NEGOCIS,
FAMILIA;
#Override
public String toString() {
return name().toUpperCase();
}
}
The thing is, I send it via a restful call and the receiving side may receive any type (so it only knows it will receive Object). So Jackson should be able to figure out the type of the argument to deserialize it.
Is it possible to do so? I was thinking that including the class name in the resulting json should allow Jackson to figure out the type, but I've been unable to do so.
I have worked over this problem for a while.
1st you could deserialize your json with Map<String, Object>. It alway works; you get standard types (your enumeration will be readed as plain string).
2nd in general case you alway know what kind of object you read. This is top-level object and you can set it to Jackson mapper: mapper.readerFor(cls).readValue(json). In case of your enumeration is a part of this cls object, then Jackson knows the type and just read value and parse to it.
3rd you actually could have multiple objects for one json string. I am talking about inheritance. And you could look at #JsonTypeInfo in Jackson documentation.
4th imagin that you read a json source and do not know what you read. In this case, you could ask Jackson to write marker at the beginning of the object. Just like you asking about class name. I think it relates to #JsonRootName. You can look on it here: Jackson JSON Deserialization with Root Element
I think that it is clean now how to work with objects in Jackson. I mean that we know how to tell Jackson what element we want to deserialize. Now we have one problem: how to serialize json -> our enumeration.
5th this is not a problem and works out of the box. Jackson uses name() method to serialize enumeration, and valueOf() to deserialize. You can look at it closer in EnumDeserializer in Jackson.
6th I do not like this behaviour, becuase it is case-sencitive. I faced with situation that when people write json string manually, the use lower-case and cannot deserialize it. Moreover, I belive, that writing enumeration constants directly to the json file is a bad practise, because If I want to refactor names of the enumeration, all existed json string should be modified as well (brrr). To solve thiese issues, I do following trick:
1. Implement EnumId interface with default implementation of parseId(String id) with using getId() to identify enumeration constants and using ignore case for compare.
1. I add id field to the enumeration
2. Add getId() - for serialization
3. Add parseId(String id) - for deserialization
4. Add new module in Jackson ObjectMapper with my customer serializer (it
should use `getId()` instead of `name()`).
if (enumId != null) {
generator.writeString(enumId.getId());
}
And tell Jackson how to deserialize this enum. Here this is dificult situation, becuase in different sources, Jackson use different deseriaization hierarchy and just adding another module to ObjectMapper with custom deserialize (just like in 4.) will not be working with all situations. To solve this problem, I found out that we could add #JsonCreator to parseId(String id) method in enumeration and Jackson will be using it in all situation.
I think that is all about this topic. I give you a code example to make it more clearly (it is better to write once, then explain twice):
public interface EnumId {
String name();
default String getId() {
return name().toLowerCase();
}
static <T extends Enum<?> & EnumId> T parseId(Class<T> cls, String id) {
T res = parseId(cls.getEnumConstants(), id, null);
if (res != null) {
return res;
}
throw new EnumConstantNotPresentException(cls, id);
}
static <T extends EnumId> T parseId(T[] values, String id, T def) {
for (T value : values) {
if (id != null ? id.equalsIgnoreCase(value.getId()) : value.getId() == null) {
return value;
}
}
return def;
}
static <T extends EnumId> T get(T value, T def) {
return value != null ? value : def;
}
}
public enum TipusViatge implements EnumId {
OCI,
NEGOCIS,
FAMILIA;
#JsonCreator
public static TipusViatge parseId(String id) {
return EnumId.parseId(TipusViatge.class, id);
}
}

Deserializing multiple fields in Jackson

Suppose I have JSON that looks something like this:
{ "key1":1, "key2":2, "key3":3 }
Where the number of "key-n" fields is unknown, but are consecutively numbered starting at 1. I wish to deserialize it into an object as follows:
public class MyPojo {
private List<Integer> keys;
}
That is, keys.get(0) corresponds to the key1 field, and so on. The JSON may have other non-"key-n" fields as well.
I had been under the impression that something like
public class MyPojo {
#JsonUnwrapped #JsonDeserialize(using = KeyDeserializer.class) private List<Integer> keys;
}
where KeyDeserializer is a JsonDeserializer would just extract all of the "key-n" fields, would work; however, I had discovered that the deserializer isn't being invoked because the JSON lacks a field named key.
Since the JSON is third-party, I can't really try to modify the JSON, so I am wondering if there are any alternate approaches to this problem.

Categories

Resources