Jackson readTree() ignore null and empty values - java

I have seen a few other posts that are similar to this so I apologise in advance if this has already been asked. However I can't get any of the solutions to work for me.
I am obtaining a chunk of JSON from a web service which I would then like to stash in Amazon's DynamoDB. However the JSON coming back has some null and empty values in it, which DynamoDB doesn't like.
So I would to remove these values from the JSON. I am currently using Jackson for JSON deserialising, via the readTree() method, as follows:
String sitesDataJson = webService.getData();
// create the object mapper and instruct it to skip null values
ObjectMapper mapper = new ObjectMapper();
mapper = mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
JsonNode jsonObj = null;
try {
jsonObj = mapper.readTree(sitesDataJson);
} catch (IOException e) {
e.printStackTrace();
}
The JSON I'm trying to read is as follows:
{
"domain_override":"",
"id":"106949",
"category_override":null,
"content_topic":null,
"modified_date":"2013-12-04 20:31:50",
"market":{
"brand_labels":{
"op":"allow_all"
},
"domains":null,
"blocked_contentattributes":{
"6":true
},
"blocked_creativetypes":{
"11":true,
"10":true,
"5":true
},
"brands":{
"ids":{
"5953":true,
"4644":true,
"8418":true,
"25480":true,
"95":true,
"5650":true
},
"op":"block"
},
"blocked_languages":{
},
"allow_unbranded_buyers":"0"
},
"type":"site",
"status":"Active",
"account_id":"100766",
"deleted":"0",
"v":"2",
"delivery_medium":"WEB",
"delivery_medium_id":"2",
"content_type_id":null,
"notes":null,
"platform_id":null,
"autorefresh_settings":{
},
"created_date":"2013-10-16 16:49:48",
"external_id":null,
}
Performing the readTree() with the NON_EMPTY or NON_NULL setting appears to make no difference. Doing a print of jsonObj using .toString() shows the empty and null values still there.
So am I going about this in the wrong way? Is there a better (or just correct!) way to do this?
I should also add that I have tried to do the same but using GSON, which I read removes these attributes by default. However that is not removing them either. So does this point to some oddity in the JSON being used?
But then I can't see how it can be as I've tried testing my code (both Jackson and GSON) with a very simple bit of JSON:
String json = "{"
+ "\"name\" : \"val1\","
+ "\"address\" : null"
+ "}";
But this still does not remove the address attribute when readTree() is used. Does this imply that the NON_NULL and NON_EMPTY settings have no effect on the readTree() method? Are they only in fact for when objects are serialised to JSON? Do I really need to read my JSON in to a POJO to be able to handle null or empty values?
Many thanks.

I am not sure 100% if the solution I am proposing will work for your case, but there is a way to use custom deserializers during the deserialization process.
StringDeserializer deserializer = new StringDeserializer();
SimpleModule module = new SimpleModule("StringDeserializerModule",
new Version(1, 0, 0, null));
module.addDeserializer(String.class, deserializer);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
Now you can add more custom desirializers for the properties you think null/ empty can be present and customize the deserialize() representation.
class StringDeserializer extends JsonDeserializer<String>
{
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
//Customize
}
}

Related

Get the fields values from an Object type object (Spring)

Under Java Spring boot, I have some returned object (of type Object) from a function having the following structure :
{id=5, name=Jasmin, description=Room Jasmin, idType=0, addedAt=2020-06-16T17:20:00.617+0000, modifiedAt=null, deleted=true, images=[string],
idBuilding=2, idFloor=4, idZone=3}
How to get the value of the id ?
I tried converting it to JSONObject but it's not working, also i tried the reflection approach :
Class<?> clazz = x.getClass();
Field field = clazz.getField("fieldName");
Object fieldValue = field.get(x);
But it's not working either returns null.
Thank you.
If you are unable to change the upstream function to return something more useful because it is from an external library or something, then creating a JsonNode (or similar) may be useful:
try {
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(x);
JsonNode jsonNode = mapper.readTree(json);
JsonNode idNode = jsonNode.get("id");
int id = idNode.asInt();
System.out.println("id = " + id);
}
catch (JsonProcessingException e) {
e.printStackTrace();
}
If the type is actually just 'Object', it does not implement Serializable and will need to be wrapped in class that does. Here is a great explanation: How to serialize a non-serializable in Java?
For reference:
https://www.baeldung.com/jackson-object-mapper-tutorial
First, create a simple POJO having a single property id Person.java
ObjectMapper mapper = new ObjectMapper();
// convertValue - convert Object of JSON to respective POJO class
GithubUsers githubUsers = mapper.convertValue(singleJsonData, Person.class);
If you fetch it using RestTemplate:
List<GithubUsers> returnValue = new ArrayList<>();
List<Object> listOfJsonData = restTemplate.getForObject("your-url", Object.class);
for (Object singleJsonData : listOfJsonData) {
ObjectMapper mapper = new ObjectMapper();
// convertValue - convert Object of JSON to respective POJO class
Person persons = mapper.convertValue(singleJsonData, Person.class);
returnValue.add(persons);
}
return returnValue;
From this, you can retrieve only id from the JSON Object.

Java: Generic serialize method

It is possible to make a generic serialize method? In which I can pass any object and serialize it acording to it's class? Something like this:
public void serializeObject(T Object) {
try (ObjectOutputStream clientOutputStream = new ObjectOutputStream(socketConnection.getOutputStream());) {
clientOutputStream.writeObject(Object);
System.out.println(user.getUsername());
//clientOutputStream.close();
} catch (Exception e) {
System.out.println(e);
}
}
You could build something like this using reflection. You inspect the object to dump; retrieve all its fields; and then try to serialize those. Of course, that will turn out to be hard, because you have to detect "cycles", to avoid serializing A pointing to B pointing to C pointing back to A for example.
And you would have to understand all the subtle issues, like having inner objects and whatnot. Seriously: this is hard.
Then: serialization isn't the even real problem in this challenge!
The problem is to rebuild objects of generic classes from the serialized data.
So, the real answer is: this is an advanced task where you can easily waste many hours of time. Meaning: you rather step back and clarify your requirements. Instead of re-inventing the wheel, you should use your energy to find an existing framework that matches your needs. Start reading here.
Try out-of-the-box mappers, e.g. Jackson JSON mapper
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
String jsonInString = mapper.writeValueAsString(yourObject);
YourClass yourObject = mapper.readValue(jsonInString, YourClass.class);
It is not possible to create Generic Serializer. Instead you can convert your object to common object that supports serialization.
For example, You can convert your object to String using GSON and serialize the string. During deserialization you pass the deserialized string object to gson to get your object back.
public <T> void serializeObject(T object) throws Exception{
Gson gson = new Gson();
String stringToSerialize = gson.toJson(object).toString();
try(ObjectOutputStream oStream = new ObjectOutputStream(new FileOutputStream("generic.ser"))){
oStream.writeObject(stringToSerialize);
}
}
public <T> T deSerializeObject(Class className) throws Exception{
Gson gson = new Gson();
try(ObjectInputStream iStream = new ObjectInputStream(new FileInputStream("generic.ser"))){
String object = (String)iStream.readObject();
return (T) gson.fromJson(object,className);
}
}
Ofcourse this solution comes with performance degrade. It completely depends on our requirement.

Which class of Jackson library should I use to parse JSON data?

My JSON data:
[
"Gi 1/1",
{
"Shutdown":true,
"Speed":"force10ModeFdx",
"AdvertiseDisabled":0,
"MediaType":"dual",
"FC":"off",
"MTU":9600,
"ExcessiveRestart":false,
"PFC":0
}
]
Controlller class method:
public #ResponseBody void setSwitchPortInterfaceInfo(#RequestBody JsonNode jsonNode)
throws JsonProcessingException, IOException { }
When calling this method only "Gi 1/1" got parsed.
Which class do I need to pass as argument to parse complete JSON object?
The JSON Data represent in the question is not present in correct format. We need to convert it into proper version than only parse the Same.
Ideal way of declaring JSON structure is:
[{"key":"value"},{"key":"value"},{"key":"value"}]
ObjectMapper objectMapper = new ObjectMapper();
String strToParse = getString(); // put the string you want to parse
JsonNode node = objectMapper.readValue(strToParse, JsonNode.class);
System.out.println(node.get(0));
System.out.println(node.get(1));

Jackson/GSON: Apply Map to POJO

Let's say I have a POJO with quite a few fields. I also have a map with a bunch of properties that would map nicely to fields in the POJO. Now I want to apply the properties in the map to my POJO. How can I do this?
Jackson provides method new ObjectMapper().convertValue(), but that creates a fresh instance of the POJO. Do I really have to do something like this?
om = new ObjectMapper();
pojoMap = om.convertValue(pojo, Map.class);
pojoMap.putAll(properties);
pojo = om.convertValue(pojoMap, Pojo.class);
Isn't there an easier way?
As I have no experience with GSON and we also have it lying around here, how would I do that with GSON?
Yes, you can create an ObjectReader that will update an existing instance from the root JSON object rather than instantiating a new one, using the readerForUpdating method of ObjectMapper:
#Test
public void apply_json_to_existing_object() throws Exception {
ExampleRecord record = new ExampleRecord();
ObjectReader reader = mapper.readerForUpdating(record)
.with(JsonParser.Feature.ALLOW_SINGLE_QUOTES)
.with(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
reader.readValue("{ firstProperty: 'foo' }");
reader.readValue("{ secondProperty: 'bar' }");
assertThat(record.firstProperty, equalTo("foo"));
assertThat(record.secondProperty, equalTo("bar"));
}
public static class ExampleRecord {
public String firstProperty;
public String secondProperty;
}
You can also create a value-updating reader from an existing ObjectReader. The following declaration seems equivalent:
ObjectReader reader = mapper.reader(ExampleRecord.class)
.withValueToUpdate(record)
.with(/* features etc */);
Addition
The above didn't actually answer your question, though.
Since you don't have the changes you want to make to the record as JSON, but rather as a map, you have to finagle things so that Jackson will read your Map. Which you can't do directly, but you can write the "JSON" out to a token buffer and then read it back:
#Test
public void apply_map_to_existing_object_via_json() throws Exception {
ExampleRecord record = new ExampleRecord();
Map<String, Object> properties = ImmutableMap.of("firstProperty", "foo", "secondProperty", "bar");
TokenBuffer buffer = new TokenBuffer(mapper, false);
mapper.writeValue(buffer, properties);
mapper.readerForUpdating(record).readValue(buffer.asParser());
assertThat(record.firstProperty, equalTo("foo"));
assertThat(record.secondProperty, equalTo("bar"));
}
(btw if this seems laborious, serializing to a token buffer and deserializing again is in fact how ObjectMapper.convertValue is implemented, so it's not a big change in functionality)

Parse JSON array with jackson where objects are strings?

I have a slightly odd question. I have created an object, let's call it a Profile, that successfully parses single JSON objects via an API that I call. There is also a multi-profile interface that will return a JSON array of Profile objects. The problem is, the multi-profile interface turns the sub objects into strings. Is there an automatic way I can tell jackson to parse these into objects?
Example of a single object:
{ "foo": "bar" }
Example of a multi object:
[ "{ \"foo\": \"bar\" }", "{ \"blah\": \"ugh\" }" ]
(Sorry can't use real data)
Notice that the sub objects are actually strings, with escaped quotes inside them.
For completeness, my code for the multi object parse looks like this:
ObjectMapper mapper = new ObjectMapper();
Profile[] profile_array = mapper.readValue(response.content, Profile[].class);
for (Profile p: profile_array)
{
String user = p.did;
profiles.put(user, p);
}
As I said, in the single-profile case, the Profile object parses. In the multi-profile case, I get this exception:
Exception: org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.xyz.id.profile.Profile, problem: no suitable creator method found to deserialize from JSON String
I suppose you'll have to create a custom deserializer and apply it to the every element of that array.
class MyCustomDeserializer extends JsonDeserializer<Profile> {
private static ObjectMapper om = new ObjectMapper();
#Override
public Profile deserialize(JsonParser jp, DeserializationContext ctxt) {
// this method is responsible for changing a single text node:
// "{ \"foo\": \"bar\" }"
// Into a Profile object
return om.readValue(jp.getText(), Profile.class);
}
}
There is no out-of-the-box support for "re-parsing" of embedded JSON-in-JSON content.
But this sounds like a possible request for enhancement (RFE)...
Have you tried using JAXB?
final ObjectMapper mapper = new ObjectMapper();
// Setting up support of JAXB
final AnnotationIntrospector introspector = new JaxbAnnotationIntrospector();
// make deserializer use JAXB annotations (only)
mapper.getDeserializationConfig().setAnnotationIntrospector(
introspector);
// make serializer use JAXB annotations (only)
mapper.getSerializationConfig().setAnnotationIntrospector(
introspector);
final StringReader stringReader = new StringReader(response);
respGetClasses = mapper.readValue(stringReader,
FooBarClass.class);
The above should get you started...
Also, you would need to mark each subclass like so:
#XmlElement(name = "event")
public List<Event> getEvents()
{
return this.events;
}

Categories