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.
Related
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
I have a Map<String, Object> map that was deserialized from a simple JSON string {"field1":"val1", "field2":"val2", "isReal":true}. I am trying to construct a Java object MyObject with the above fields.
I can do it using com.fasterxml.jackson.databind.ObjectMapper like so:
public static MyObject load(Map<String, Object> map) throws IOException {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(new ObjectMapper().writeValueAsString(map), MyObject.class);
}
I was wondering if anyone knows how to do the same using com.jsoniter library?
I have tried to use JsonIterator.deserialize, but that doesn't take a Map as input.
I have also seen ReflectionEncoderFactory usage on the library website, but I don't fully grasp as to how I would use it to construct an object MyObject
As per #Scary Wombat's explanation:
First it is necessary to convert the Map back to JSON string and then convert the JSON string to an object:
public static MyObject load(Map<String, Object> map) throws IOException {
return JsonIterator.deserialize(JsonStream.serialize(map), MyObject.class);
}
JsonStream.serialize(map) is the same new ObjectMapper().writeValueAsString(map)
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)
I have a JSON structure that incorporates a wrapping level that I don't have in my POJOs. Like so:
JSON:
{
"category1": {
"cat1Prop1": "c1p1",
"cat1Prop2": "c1p2",
"cat1Prop3": "c1p3"
},
"category2": {
"cat2Prop1": "c2p1",
"cat2Prop2": "c2p2"
},
"category3": {
"cat3Prop1": "c3p1",
"cat3Prop2": "c3p2",
"cat3Prop3": "c3p3"
},
"category4": {
"cat4Prop1": "c4p1"
}
}
POJO:
public class MyPojo {
private String cat1Prop1;
private String cat1Prop2;
private String cat1Prop3;
private String cat2Prop1;
private String cat2Prop2;
private String cat3Prop1;
private String cat3Prop2;
private String cat3Prop3;
private String cat4Prop1;
// Getters / setters, etc...
}
As you can see, the JSON have a "category" level (that for different reasons I don't want to have in my Pojo).
I'm looking for a way to use Jackson for serializaion/deserialization to handle this in a smooth way.
I'm aware that Jackson has a #JsonUnwrapped annotation that kind of handles the opposite. I'm also aware that there is a feature request for a "#JsonWrapped" annotation that I think would solve my case.
Thankful for any input or help regarding this, as I have been looking around quite a bit. Also, any suggestions on how this could be accomplished using any other library (like gson, flexjson, etc) is also interesting.
You can try with this algorithm:
Read JSON as Map.
Flatten map
Use ObjectMapper to convert Map into POJO.
Implementation could looks like this:
ObjectMapper mapper = new ObjectMapper();
Map<String, Map<String, String>> map = mapper.readValue(new File("X:/test.json"), Map.class);
Map<String, String> result = new HashMap<String, String>();
for (Entry<String, Map<String, String>> entry : map.entrySet()) {
result.putAll(entry.getValue());
}
System.out.println(mapper.convertValue(result, MyPojo.class));
I want to send something like this from the client to the rest service
jsonObj =
{
"info" : {
"field1" : "val1"..
.....
"fieldN" : "valN"..
}
}
And I am not sure how can I handle this using a rest service using Jersey and Jackson in Java
I do not want to create a new info class with using Jackson properties with N field as they are going to change always. I just want to grab the jsonObject which is inside the jsonObject and operate on that as JsonObject.
Any thoughts ?
Assuming you have a root object that you are reading the result into, you could define docInfo as a Map<String, Object> within your jsonObj. This will probably work, but I can't give it a go presently.
If you don't have a root object you can just use a Map<String, Object> as your root object and play with it from there. That Map could contain other maps for nested json objects.
Yes, use a wrapper object with an embedded map, as #digitialjoel suggested. This is a concrete example:
class DocInfo {
private Map<String, Object> docInfo;
public DocInfo() {
super();
}
public DocInfo(final Map<String, Object> docInfo) {
super();
this.docInfo = docInfo;
}
// Getters, setters
}
final Map<String, Object> data = new LinkedHashMap<String, Object>(4);
data.put("field1", "value1");
data.put("field2", "value2");
data.put("field3", "value3");
data.put("field4", "value4");
final DocInfo info = new DocInfo(data);
final ObjectMapper mapper = new ObjectMapper();
final String json = mapper.writeValueAsString(info);
System.out.println(json);
Output:
{"docInfo":{"field1":"value1","field2":"value2","field3":"value3","field4":"value4"}}