Jackson/GSON: Apply Map to POJO - java

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)

Related

Discard serializing a map entry with a specific key

I'm having a map that I need to serialize but when I serialize it I get jackson infinite recursion (stackoverflowerror). So, after debugging a while I found the entry that caused this problem but this entry is a list which contains other objects that have some cyclic dependencies.
Let me write a sample code here.
Map<String, ?> params = new HashMap();
// Create some list and add objects with cyclic dependencies to it.
List<ContentObject> problematicList = new ArrayList();
//list.addAll(some problematic objects);
params.put("contentsModified", problematicList);
objectmapper.writeValueAsString(params); // here I get jackson infinite recursion (stackoverflowerror)
I tried marking the classes of the objects I found in the problematicList using #JsonIgnoreType but it didn't work. Is there any way to write some custom serializer to see if a map contains an entry with the name contentsModified and if it finds such entry it doesn't serialize the problematic lists?
You should fix the actual bi-directional mapping with problematic classes when serializing. For example if you have classes ContentObject & BackReferringObject referring to each other you can use #JsonBackReference like:
public class ContentObject {
private String name = "contentsModified";
private BackReferringObject backReferringObject;
}
public class BackReferringObject {
#JsonBackReference
private ContentObject contentObject;
}
This tells Jackson to not serialize contentObject back again.
If this is not possible you can always write some custom serializer. That would make things more complicated. For example, you could have something like this:
public class ContentObjectSerializer extends JsonSerializer<ContentObject> {
private final ObjectMapper objectMapper = new ObjectMapper();
#Override
public void serialize(ContentObject value, JsonGenerator gen,
SerializerProvider serializers)
throws IOException {
if (!value.getName().equals("contentsModified")) {
var sw = new StringWriter();
objectMapper.writeValue(sw, value);
gen.writeRawValue(sw.toString());
}
}
}
and usage:
var objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(ContentObject.class, new ContentObjectSerializer());
objectMapper.registerModule(module);
The latter configuration is really needed. You cannot for example annotate your ContentObject with #JsonSerialize(using = ContentObjectSerializer.class) since then the ObjectMapper inside custom serializer would cause another recursive call chain to custom serializer and cause stack overflow again.
I realize this is not directly applicable to a Map but as an example

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.

Add a parent node to Json output in java

I have converted some info to Json format using Jackson in Java. Below is the output I get
[{"lat":45.9,"lon":10.9,"title":"Title A1","html":"<h3>Content A1</h3>","icon":"http://maps.google.com/mapfiles/markerA.png"},{"lat":44.8,"lon":1.7,"title":"Title B1","html":"<h3>Content B1</h3>","icon":"http://maps.google.com/mapfiles/markerB.png","show_infowindow":false},{"lat":51.5,"lon":-1.1,"title":"Title C1","html":"<h3>Content C1</h3><p>Lorem Ipsum..</p>","zoom":8,"icon":"http://maps.google.com/mapfiles/markerC.png"}]
My question is how can I get it in the below format, basically adding the Json to a root node which called locations
{"locations":[{"lat":45.9,"lon":10.9,"title":"Title A1","html":"<h3>Content A1</h3>","icon":"http://maps.google.com/mapfiles/markerA.png"},{"lat":44.8,"lon":1.7,"title":"Title B1","html":"<h3>Content B1</h3>","icon":"http://maps.google.com/mapfiles/markerB.png","show_infowindow":false},{"lat":51.5,"lon":-1.1,"title":"Title C1","html":"<h3>Content C1</h3><p>Lorem Ipsum..</p>","zoom":8,"icon":"http://maps.google.com/mapfiles/markerC.png"}]}
You may wrap the array into a JSONObject like so
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = new HashMap<String, Object>();
String json = jsonArray.toString();
map.put("locations", json);
json = mapper.writeValueAsString(map);
you can achieve this by following changes.
Let's Assume, your JSON will be created based upon Bean.java class likewise,
[{"lat":45.9,"lon":10.9,"title":"Title A1","html":"<h3>Content A1</h3>","icon":"http://maps.google.com/mapfiles/markerA.png"},{"lat":44.8,"lon":1.7,"title":"Title B1","html":"<h3>Content B1</h3>","icon":"http://maps.google.com/mapfiles/markerB.png","show_infowindow":false},{"lat":51.5,"lon":-1.1,"title":"Title C1","html":"<h3>Content C1</h3><p>Lorem Ipsum..</p>","zoom":8,"icon":"http://maps.google.com/mapfiles/markerC.png"}]
Now, As per your new requirement, you want something likewise,
{"locations":[{"lat":45.9,"lon":10.9,"title":"Title A1","html":"<h3>Content A1</h3>","icon":"http://maps.google.com/mapfiles/markerA.png"},{"lat":44.8,"lon":1.7,"title":"Title B1","html":"<h3>Content B1</h3>","icon":"http://maps.google.com/mapfiles/markerB.png","show_infowindow":false},{"lat":51.5,"lon":-1.1,"title":"Title C1","html":"<h3>Content C1</h3><p>Lorem Ipsum..</p>","zoom":8,"icon":"http://maps.google.com/mapfiles/markerC.png"}]}
So, in this case you need to create one more class, let's say it's SuperBean.java then it should be likewise,
public class SuperBean {
private Bean [] locations;
public Bean[] getBean() {
return locations;
}
public void setBean(Bean[] locations) {
this.locations = locations;
}
}
So, your JSON will be created likewise,
{"locations":[......]} // as per your requirement.

Serialize HashMap to a JSON string while avoiding certain fields in Java

I have a map as below:
Map map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
I need to convert this map into a JSON string. I know that this can be done using Jackson as below:
new ObjectMapper().writeValueAsString(map);
The problem is, I do not want to map key3 to the String. The output String should be as below:
{"key1":"value1","key2":"value2"}
Is there any way to avoid certain fields from the HashMap while serializing it to a String? Moreover, I want to add certain fields to the String while serializing. For instance, I need to add a field called "key4" with a value "value4". Thus, the final String should be:
{"key1":"value1","key2":"value2","key4":"value4"}
How do I do this using Jackson? Or is there any other way to do this in Java?
If you want to serialize a HashMap like this, you should implement a custom serializer. Here is one example:
public class CustomSerializer extends StdSerializer<Map> {
protected CustomSerializer() {
super(Map.class);
}
#Override
public void serialize(Map map, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException {
jsonGenerator.writeStartObject();
for (Object key : map.keySet()) {
if(!"key3".equals(key)){
jsonGenerator.writeStringField((String) key, (String) map.get(key));
}
}
jsonGenerator.writeStringField("key4","value4");
jsonGenerator.writeEndObject();
}
}
public class Main {
public static void main(String[] args) throws Exception{
Map<String, String> map = new HashMap<>();
map.put("key1","value1");
map.put("key2","value2");
map.put("key3","value3");
ObjectMapper mapper = new ObjectMapper();
SimpleModule serializerModule = new SimpleModule("SerializerModule", new Version(1, 0, 0, null, "mt", "customSerializerTest"));
serializerModule.addSerializer(new CustomSerializer());
mapper.registerModule(serializerModule);
System.out.println(mapper.writeValueAsString(map));
}
}
Output:
{"key1":"value1","key2":"value2","key4":"value4"}
If you are serializing a POJO, you can use the #JsonIgnore annotation, but in this case, this is not an option, so you should make a copy of the HashMap, and do whatever operations on it, what you need.
The simplest way really is to modify the Map.
However, if you want, you can actually use #JsonIgnoreProperties on Map type as well. So, something like:
#JsonIgnoreProperties({ "key3" })
public class MyFilteringMap extends HashMap<String,String> { }
It is not possible to add properties via annotations.
But it is possible to use #JsonAnySetter and #JsonAnyGetter annotations to make a POJO look like a Map. This would allow combining of approaches for filtering, as well as some "standard" properties and other misc properties. See this -- http://www.cowtowncoder.com/blog/archives/2011/07/entry_458.html -- for details.
You can try Jackson Custom Serializers - http://wiki.fasterxml.com/JacksonHowToCustomSerializers. I think it's convenient option for your use case. Using own serializer you can make transparent object processing in serialize method - for example you can copy map to another avoiding certain fields etc.

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