Jackson: serialize empty or null HashMap to empty Array - java

I have this class whose attributes should be serialized to JSon using Jackson:
public class MicroClinicalInfo {
#JsonProperty
private Map<String, String> clinicalInfo;
#JsonCreator
public MicroClinicalInfo() {}
public MicroClinicalInfo(DtoMicroSampleInfo dto) {
this.clinicalInfo = new HashMap<String, String>();
for(/* each clinical info entity received from EJB in dto */) {
this.clinicalInfo.put(clinicalInfoKey, clinicalInfoDescr);
}
}
}
Now, if clinical info do not exist, produced JSon is {"clinicalInfo":{}} but in this case frontend developer wants me to return an empty array and not an empty object. So, I should return {"clinicalInfo":[]}. How can I do that?
I was trying with:
#JsonGetter
public Map<String, String> getClinicalInfo() {
if (this.clinicalInfo != null && !this.clinicalInfo.isEmpty()) {
return this.clinicalInfo;
}
else {
}
}
but I don't know how to give a uniform vision. Because serialization of a not empty HashMap is correct, but I don't know how to render it as an empty array.
Thank you all

Related

How do I have two identical json property names assigned to two separated fields

I have the following Java class
I need to serialize it to json in the following way:
if the list(paymentTransactionReport field) is not null display it values -
{
"paymentTransactionResponse" : [
{},
{}
]
}
if the list is null I need to display the paymentTransactionReportError in json field with name 'paymentTransactionResponse', as in previous case. Example -
{
"paymentTransactionResponse" : {
----
//fields from PaymentTransactionReportError class
----
}
}
How can I do this?preferably without custom serializers.
If use just two annotations #JsonProperty with the same name and JsonInclude.NON_NULL as I did, I have this error: No converter found for return value of type:... Seems to be that is a error happened during serialization because of fields with the same name
One way you can achieve this is, using #JsonAnyGetter, Try this
public class TestDTO {
#JsonIgnore
List<String> paymentTransactionResponse;
#JsonIgnore
String paymentTransactionResponseError;
public List<String> getPaymentTransactionResponse() {
return paymentTransactionResponse;
}
public void setPaymentTransactionResponse(List<String> paymentTransactionResponse) {
this.paymentTransactionResponse = paymentTransactionResponse;
}
public String getPaymentTransactionResponseError() {
return paymentTransactionResponseError;
}
public void setPaymentTransactionResponseError(String paymentTransactionResponseError) {
this.paymentTransactionResponseError = paymentTransactionResponseError;
}
#JsonAnyGetter
public Map<String, Object> getData(){
Map<String, Object> map = new HashMap<String, Object>();
if(paymentTransactionResponse != null) {
map.put("paymentTransactionResponse", paymentTransactionResponse);
}else {
map.put("paymentTransactionResponse", paymentTransactionResponseError);
}
return map;
}}

How to deal with nested static casting?

I simply have a Map. But it can return a Map, which may also return a Map. It's possible up to 3 to 4 nested Maps. So when I want to access a nested value, I need to do this:
((Map)((Map)((Map)CacheMap.get("id")).get("id")).get("id")).get("id")
Is there a cleaner way to do this?
The reason I'm using a Map instead of mapping it to an object is for maintainability (e.g. when there are new fields).
Note:
Map<String, Object>
It has to be Object because it won't always return a Hashmap. It may return a String or a Long.
Further clarification:
What I'm doing is I'm calling an api which returns a json response which I save as a Map.
Here's some helper methods that may help things seem cleaner and more readable:
#SuppressWarnings("unchecked")
public static Map<String, Object> getMap(Map<String, Object> map, String key) {
return (Map<String, Object>)map.get(key);
}
#SuppressWarnings("unchecked")
public static String getString(Map<String, Object> map, String key) {
return (String)map.get(key);
}
#SuppressWarnings("unchecked")
public static Integer geInteger(Map<String, Object> map, String key) {
return (Integer)map.get(key);
}
// you can add more methods for Date, Long, and any other types you know you'll get
But you would have to nest the calls:
String attrValue = getString(getMap(getMap(map, id1), id2), attrName);
Or, if you want something more funky, add the above methods as instance methods to a map impl:
public class FunkyMap extends HashMap<String, Object> {
#SuppressWarnings("unchecked")
public FunkyMap getNode(String key) {
return (FunkyMap)get(key);
}
#SuppressWarnings("unchecked")
public String getString(String key) {
return (String)get(key);
}
#SuppressWarnings("unchecked")
public Integer geInteger(String key) {
return (Integer)get(key);
}
// you can add more methods for Date, Long, and any other types you know you'll get
}
Deserialize into this class with your json library (you'll probably have to provide it with a factory method for the map class impl), then you can chain the calls more naturally:
String attrValue = map.getNode(id1).getNode(id2).getString(attrName);
The funky option is what I did for a company, and it worked a treat :)
If you don't know the depth of the JSON tree and if you worry about maintainability if new fields are added, I would recommend not to deserialize the full tree in a Map but instead use a low-level parser.
For example, if your JSON looks like the following:
{
"id": {
"id": {
"id": {
"id": 22.0
}
}
}
}
You could write something like that to get the id using Jackson:
public Object getId(String json) throws JsonParseException, IOException
{
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
JsonNode id = root.get("id");
while (id != null && id.isObject())
{
id = id.get("id");
}
//Cannot find a JsonNode for the id
if (id == null)
{
return null;
}
//Convert id to either String or Long
if (id.isTextual())
return id.asText();
if (id.isNumber())
return id.asLong();
return null;
}

Jackson deserialize extra fields as Map

I'm looking to deserialize any unknown fields in a JSON object as entries in a Map which is a member of a POJO.
For example, here is the JSON:
{
"knownField" : 5,
"unknownField1" : "926f7c2f-1ae2-426b-9f36-4ba042334b68",
"unknownField2" : "ed51e59d-a551-4cdc-be69-7d337162b691"
}
Here is the POJO:
class MyObject {
int knownField;
Map<String, UUID> unknownFields;
// getters/setters whatever
}
Is there a way to configure this with Jackson? If not, is there an effective way to write a StdDeserializer to do it (assume the values in unknownFields can be a more complex but well known consistent type)?
There is a feature and an annotation exactly fitting this purpose.
I tested and it works with UUIDs like in your example:
class MyUUIDClass {
public int knownField;
Map<String, UUID> unknownFields = new HashMap<>();
// Capture all other fields that Jackson do not match other members
#JsonAnyGetter
public Map<String, UUID> otherFields() {
return unknownFields;
}
#JsonAnySetter
public void setOtherField(String name, UUID value) {
unknownFields.put(name, value);
}
}
And it would work like this:
MyUUIDClass deserialized = objectMapper.readValue("{" +
"\"knownField\": 1," +
"\"foo\": \"9cfc64e0-9fed-492e-a7a1-ed2350debd95\"" +
"}", MyUUIDClass.class);
Also more common types like Strings work:
class MyClass {
public int knownField;
Map<String, String> unknownFields = new HashMap<>();
// Capture all other fields that Jackson do not match other members
#JsonAnyGetter
public Map<String, String> otherFields() {
return unknownFields;
}
#JsonAnySetter
public void setOtherField(String name, String value) {
unknownFields.put(name, value);
}
}
(I found this feature in this blog post first).

Jackson, custom deserialization for specific field names

I would like to know if it is possible to customize the deserialization of json depending the field name, for example
{
id: "abc123",
field1: {...}
other: {
field1: {....}
}
}
I the previous json, I would like to have a custom deserializer for the fields named "field1", in any level in the json.
The reason: We have our data persisted as JSON, and we have a REST service that returns such data, but before return it, the service must inject extra information in the "field1" attribute.
The types are very dynamic, so we cannot define a Java class to map the json to use annotations.
An first approach was to deserialize to Map.class and then use JsonPath to search the $..field1 pattern, but this process is expensive for bigger objects.
I appreciate any help.
Thanks,
Edwin Miguel
You should consider registering a custom deserializer with the ObjectMapper for this purpose.
Then you should be able to simply map your JSON stream to a Map<String, Object> knowing that your field1 objects will be handled by your custom code.
I create a custom deserializer and added it to a SimpleModule for the ObjectMapper
public class CustomDeserializer extends StdDeserializer<Map> {
public CustomDeserializer() {
super(Map.class);
}
#Override
public Map deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Map<String, Object> result = new HashMap<String, Object>();
jp.nextToken();
while (!JsonToken.END_OBJECT.equals(jp.getCurrentToken())) {
String key = jp.getText();
jp.nextToken();
if ("field1".equals(key)) {
MyObject fiedlObj= jp.readValueAs(MyObject.class);
//inject extra info
//...
result.put(key, fieldObj);
} else {
if (JsonToken.START_OBJECT.equals(jp.getCurrentToken())) {
result.put(key, deserialize(jp, ctxt));
} else if (JsonToken.START_ARRAY.equals(jp.getCurrentToken())) {
jp.nextToken();
List<Object> linkedList = new LinkedList<Object>();
while (!JsonToken.END_ARRAY.equals(jp.getCurrentToken())) {
linkedList.add(deserialize(jp, ctxt));
jp.nextToken();
}
result.put(key, linkedList);
} else {
result.put(key, jp.readValueAs(Object.class));
}
}
jp.nextToken();
}
return result;
}
}
The problem is that I had to implement the parsing for the remaining attributes.
For now, it is my solution...

jackson delay deserializing field

I have a class like this:
public class DeserializedHeader
int typeToClassId;
Object obj
I know what type of object obj is based on the typeToClassId, which is unfortunately only known at runtime.
I want to parse obj out based on typeToClassId - what's the best approach here? Annotations seem like they're out, and something based on ObjectMapper seems right, but I'm having trouble figuring out what the best approach is likely to be.
Something along the lines of
Class clazz = lookUpClassBasedOnId(typeToClassId)
objectMapper.readValue(obj, clazz)
Obviously, this doesn't work since obj is already deserialized... but could I do this in 2 steps somehow, perhaps with convertValue?
This is really complex and painful problem. I do not know any sophisticated and elegant solution, but I can share with you my idea which I developed. I have created example program which help me to show you how you can solve your problem. At the beginning I have created two simple POJO classes:
class Product {
private String name;
// getters/setters/toString
}
and
class Entity {
private long id;
// getters/setters/toString
}
Example input JSON for those classes could look like this. For Product class:
{
"typeToClassId" : 33,
"obj" : {
"name" : "Computer"
}
}
and for Entity class:
{
"typeToClassId" : 45,
"obj" : {
"id" : 10
}
}
The main functionality which we want to use is "partial serializing/deserializing". To do this we will enable FAIL_ON_UNKNOWN_PROPERTIES feature on ObjectMapper. Now we have to create two classes which define typeToClassId and obj properties.
class HeaderType {
private int typeToClassId;
public int getTypeToClassId() {
return typeToClassId;
}
public void setTypeToClassId(int typeToClassId) {
this.typeToClassId = typeToClassId;
}
#Override
public String toString() {
return "HeaderType [typeToClassId=" + typeToClassId + "]";
}
}
class HeaderObject<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
#Override
public String toString() {
return "HeaderObject [obj=" + obj + "]";
}
}
And, finally source code which can parse JSON:
// Simple binding
Map<Integer, Class<?>> classResolverMap = new HashMap<Integer, Class<?>>();
classResolverMap.put(33, Product.class);
classResolverMap.put(45, Entity.class);
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
String json = "{...}";
// Parse type
HeaderType headerType = mapper.readValue(json, HeaderType.class);
// Retrieve class by integer value
Class<?> clazz = classResolverMap.get(headerType.getTypeToClassId());
// Create dynamic type
JavaType type = mapper.getTypeFactory().constructParametricType(HeaderObject.class, clazz);
// Parse object
HeaderObject<?> headerObject = (HeaderObject<?>) mapper.readValue(json, type);
// Get the object
Object result = headerObject.getObj();
System.out.println(result);
Helpful links:
How To Convert Java Map To / From JSON (Jackson).
java jackson parse object containing a generic type object.

Categories

Resources