How to deserialize a Map<?, ?> with Jackson? - java

I'm trying to serialize/deserialize a Map<?, ?> with arbitrary object as keys with Jackson version 2.8. The JSON counterpart should be an array of couples, i.e. given
public class Foo {
public String foo;
public Foo(String foo) {
this.foo = foo;
}
}
public class Bar {
public String bar;
public Bar(String bar) {
this.bar = bar;
}
}
then
Map<Foo, Bar> map;
map.put(new Foo("foo1"), new Bar("bar1"));
map.put(new Foo("foo2"), new Bar("bar2"));
should be represented by this JSON
[
[ { "foo": "foo1" }, { "bar": "bar1" } ],
[ { "foo": "foo2" }, { "bar": "bar2" } ]
]
So I did the serializer part as
public class MapToArraySerializer extends JsonSerializer<Map<?, ?>> {
#Override
public void serialize(Map<?, ?> value, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException {
gen.writeStartArray();
for (Map.Entry<?, ?> entry : value.entrySet()) {
gen.writeStartArray();
gen.writeObject(entry.getKey());
gen.writeObject(entry.getValue());
gen.writeEndArray();
}
gen.writeEndArray();
}
}
but I have no idea how to write a JsonDeserializer to do the inverse job. Any suggestions?
Note: I need the [ [ "key1", "value1" ], [ "key2", "value2" ] ] notation to be able to consume that JSON in JavaScript a new Map( ... ) and JSON.stringify(map) would produce that notation too (see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map).
To clarify, such a map would be a field of other classes, e.g.
public class Baz {
#JsonSerialize(using = MapToArraySerializer.class)
#JsonDeserialize(using = ArrayToMapDeserializer.class, keyAs = Foo.class, contentAs = Bar.class)
Map<Foo, Bar> map;
}
and ArrayToMapDeserializer extends JsonDeserializer<Map<?, ?>> is where I'm asking for help.

I came up with this solution:
public class ArrayToMapDeserializer extends JsonDeserializer<SortedMap<Object, Object>>
implements ContextualDeserializer {
private Class<?> keyAs;
private Class<?> contentAs;
#Override
public Map<Object, Object> deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
return this.deserialize(p, ctxt, new HashMap<>());
}
#Override
public Map<Object, Object> deserialize(JsonParser p, DeserializationContext ctxt,
Map<Object, Object> intoValue) throws IOException, JsonProcessingException {
JsonNode node = p.readValueAsTree();
ObjectCodec codec = p.getCodec();
if (node.isArray()) {
node.forEach(entry -> {
try {
JsonNode keyNode = entry.get(0);
JsonNode valueNode = entry.get(1);
intoValue.put(keyNode.traverse(codec).readValueAs(this.keyAs),
valueNode.traverse(codec).readValueAs(this.contentAs));
} catch (NullPointerException | IOException e) {
// skip entry
}
});
}
return intoValue;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
JsonDeserialize jsonDeserialize = property.getAnnotation(JsonDeserialize.class);
this.keyAs = jsonDeserialize.keyAs();
this.contentAs = jsonDeserialize.contentAs();
return this;
}
}
which can be used like this:
public class Baz {
#JsonSerialize(using = MapToArraySerializer.class)
#JsonDeserialize(using = ArrayToMapDeserializer.class,
keyAs = Foo.class, contentAs = Bar.class)
Map<Foo, Bar> map;
}

Here is the deserialize:
#Override
public Map<?, ?> deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
Map map = new LinkedHashMap();
ObjectCodec oc = p.getCodec();
JsonNode anode = oc.readTree(p);
for (int i = 0; i < anode.size(); i++) {
JsonNode node = anode.get(i);
map.put(node.get(0), node.get(1));
}
return map;
}
I added a few test cases, with a new Oson implementation, to the original solution, in which I used oson to do the conversion, but with a different convension: map to json: {key1: value1, key2: value2, ...}, so the json output becomes:
{
{
"foo": "foo1"
}: {
"bar": "bar1"
},
{
"foo": "foo2"
}: {
"bar": "bar2"
}
}
You can check out the source code!

Related

Serialize, deserialize using jackson

I was trying convert to my object to and from json but the default serializer, deserializer by jackson doesn't work.
How can I make this work? I understand I might need to write a custom serializer, deserializer. How can I do that?
Is ther some annotation by adding which the code would work?
Here is the object:
#JsonDeserialize(keyUsing = mypairDeserializer.class)
#JsonSerialize(keyUsing = mypairSerializer.class)
HashMap<Set < Mypair > , List < Mypair > > obj;
public class ConditionSerializer extends JsonSerializer<Collection<mypair>> {
#Override
public void serialize(final Collection<mypair> conditionSet, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("Pair");
jsonGenerator.writeStartArray();
for(final Condition condition: conditionSet) {
jsonGenerator.writeString(mypair.toString());
}
jsonGenerator.writeEndArray();
jsonGenerator.writeEndObject();
}
}
public class mypairDeserializer extends KeyDeserializer {
ObjectMapper mapper = new ObjectMapper();
#Override
public Collection<mypair> deserializeKey(final String key, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
// return new mypair(key);
return mapper.readValue(key, Collection.class);
}
}
Hi again from last post,
So, this is an example of what you can do :
Note that since I don't know what is your object Mypair, I did this example with a User class :
public class User {
private int id;
private String name;
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
// getters & setters
}
The class containing your complex object :
public class YourClass {
#JsonSerialize(using = ComplexObjectSerializer.class)
private Map<Set<User>, List<User>> object;
public YourClass(Map<Set<User>, List<User>> object) {
this.object = object;
}
public Map<Set<User>, List<User>> getObject() {
return object;
}
public void setObject(Map<Set<User>, List<User>> object) {
this.object = object;
}
}
The custom serializer :
public class ComplexObjectSerializer extends StdSerializer<Map<Set<User>, List<User>>> {
public ComplexObjectSerializer() {
this(null);
}
public ComplexObjectSerializer(Class<Map<Set<User>, List<User>>> t) {
super(t);
}
private static final long serialVersionUID = 1L;
#Override
public void serialize(Map<Set<User>, List<User>> complexObject,
JsonGenerator jsonGen, SerializerProvider arg2) throws IOException {
// Suppose you want the following json:
/**
* [ { "set":[], "list":[] } ]
*/
jsonGen.writeStartArray(); // [
for (Entry<Set<User>, List<User>> entry : complexObject.entrySet()) {
jsonGen.writeStartObject(); // {
jsonGen.writeObjectField("set", entry.getKey()); // It will call the default serializer for a Set<User>, ie : [ {"id": 0, "name":"string"} ]
jsonGen.writeObjectField("list", entry.getValue()); // It will call the default serializer for a List<User>, ie the same thing as the Set above
jsonGen.writeEndObject(); // }
}
jsonGen.writeEndArray(); // ]
}
}
Main :
Map<Set<User>, List<User>> complexObject = new HashMap<Set<User>, List<User>>();
// Add some data in the map ...
YourClass yourClass = new YourClass(complexObject);
// Serialize your object
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(yourClass); // It will call your custom serializer
System.out.println(json);
Output :
{
"object": [
{
"set": [
{
"id": 5,
"name": "userName5"
},
{
"id": 6,
"name": "userName6"
}
],
"list": [
{
"id": 2,
"name": "userName2"
}
]
},
{
"set": [
{
"id": 4,
"name": "userName4"
},
{
"id": 3,
"name": "userName3"
}
],
"list": [
{
"id": 0,
"name": "userName0"
},
{
"id": 1,
"name": "userName1"
}
]
}
]
}

Jackson java.util.Date value in Map<String, Object> (de-)serialization

Consider this property
#JsonProperty
private Map<String, Object> myMap;
When a contained java.util.Date value is serialized as long, it will not be deserialized to Date again because the type information is not present in Map<String, Object>. How can I bypass the problem? I read answers about this question which would be a work around but there would be no way to distinguish strings containing dates from dates serialized as strings in the map. Can I tell Jackson to include type information for each map value such that Jackson can deserialize them correctly?
Implement a custom Deserializer and add the Annotation #JsonDeserialize(using = DateDeserializer.class) to your field.
Take a look at this example:
Your Json-Bean:
public class Foo {
private String name;
#JsonProperty
#JsonDeserialize(using = DateDeserializer.class)
private Map<String, Object> dates;
[...] // getter, setter, equals, hashcode
}
Deserializer:
public class DateDeserializer extends JsonDeserializer<Map<String, Object>> {
private TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {};
#Override
public Map<String, Object> deserialize(JsonParser p, DeserializationContext ctxt, Map<String, Object> target) throws IOException, JsonProcessingException {
Map<String, Long> map = new ObjectMapper().readValue(p, typeRef);
for(Entry<String, Long> e : map.entrySet()){
Long value = e.getValue();
String key = e.getKey();
if(value instanceof Long){ // or if("date".equals(key)) ...
target.put(key, new Date(value));
} else {
target.put(key, value); // leave as is
}
}
return target;
}
#Override
public Map<String, Object> deserialize(JsonParser paramJsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return this.deserialize(paramJsonParser, ctxt, new HashMap<>());
}
}
Simple test:
public static void main(String[] args) throws Exception {
Foo foo1 = new Foo();
foo1.setName("foo");
foo1.setData(new HashMap<String, Object>(){{
put("date", new Date());
put("bool", true);
put("string", "yeah");
}});
ObjectMapper mapper = new ObjectMapper();
String jsonStr = mapper.writeValueAsString(foo1);
System.out.println(jsonStr);
Foo foo2 = mapper.readValue(jsonStr, Foo.class);
System.out.println(foo2.equals(foo1));
}
Finally, I came up with this solution. Deserializer:
private TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>() {
};
#Override
public Map<String, Object> deserialize(JsonParser p, DeserializationContext ctxt, Map<String, Object> target) throws IOException {
Map<String, Object> map = new ObjectMapper().readValue(p, typeRef);
for (Map.Entry<String, Object> e : map.entrySet()) {
if (e.getKey().endsWith("[date]")) {
target.put(e.getKey().substring(0, e.getKey().length() - 6), new Date((Long) e.getValue()));
}
else {
target.put(e.getKey(), e.getValue());
}
}
return target;
}
Serializer:
#Override
public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
Map<String, Object> adaptedValue = new HashMap<>(value);
for (Map.Entry<String, Object> e : value.entrySet()) {
if (e.getValue() instanceof Date) {
adaptedValue.put(e.getKey() + "[date]", ((Date) e.getValue()).getTime());
adaptedValue.remove(e.getKey());
}
}
new ObjectMapper().writeValue(gen, adaptedValue);
}
The map key is adapted dependent on the data type. This is easily extendable.

How to create array field in by jackson based on json property prefix? [duplicate]

This question already has answers here:
How to parse a JSON string to an array using Jackson
(4 answers)
Closed 6 years ago.
Say I have the following json string:
{
"name": "Foo"
"pic1": "some pic 1",
"pic2": "some pic 2",
"pic3": "some pic 3",
...
"picn": "some pic n"
}
I need to create following POJO:
class Foo {
String name;
String[] pics;
}
from this string.
The trick is I need to map pic* to String[] pics somehow.
How can I do that using Jackson?
Add your own custom serializer and deserializer.
#JsonSerialize(using = MySerializer.class)
#JsonDeserialize(using = MyDeSerializer.class)
public class Foo {
private String name;
private String[] pics;
Serializer:
public class MySerializer extends JsonSerializer<Foo> {
#Override
public void serialize(Foo value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
int i = 1;
String fieldName = "pics";
jgen.writeStartObject();
jgen.writeStringField("name", value.getName());
for (String stringValue : value.getPics()) {
jgen.writeStringField(fieldName + i, stringValue);
i++;
}
jgen.writeEndObject();
}
}
Deserializer:
public class MyDeSerializer extends JsonDeserializer<Foo> {
#Override
public Foo deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectCodec objectCodec = jp.getCodec();
Foo foo = new Foo();
JsonNode node = objectCodec.readTree(jp);
Iterator<Entry<String, JsonNode>> fields = node.fields();
String[] pics = new String[node.size() - 1];
int i = 0;
while (fields.hasNext()) {
Entry<String, JsonNode> next = fields.next();
if (next.getKey().equalsIgnoreCase("name"))
foo.setName(node.get("name").asText());
else {
pics[i] = node.get(next.getKey()).asText();
i++;
}
}
foo.setPics(pics);
return foo;
}
}

json jackson unusual map serialization

Assume, I have the following structure:
public class SomeClass {
private String id;
#JsonProperty("key-value")
private Map<String, Object> keyValue;}
Obviously, it will be serialized to
{
"id" : "id1",
"key-value" :
{"key1" : "value1",
"key2 : "value2"}
}
Is it possible to represent it like this?
{
"id" : "id1",
"key1" : "value1",
"key2 : "value2"
}
Thanks in advance!
It is quite possible with the help of Jackson's custom serializer:
add the #JsonSerialize annoation to your POJO:
(also added necessary ctor and getters)
#JsonSerialize(using = SomeClassSerializer.class)
public static class SomeClass {
private String id;
#JsonProperty("key-value")
private Map<String, Object> keyValue;
public SomeClass(String id, Map<String, Object> keyValue) {
this.id = id;
this.keyValue = keyValue;
}
public String getId() { return id; }
public Map<String, Object> getKeyValue() { return keyValue; }
}
the custom serializer looks like this:
:
public class SomeClassSerializer extends JsonSerializer<SomeClass>
{
#Override
public void serialize(SomeClass sc, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException
{
gen.writeStartObject();
// write id propertry
gen.writeStringField("id", sc.getId());
// loop on keyValue entries, write each as property
for (Map.Entry<String, Object> keyValueEntry : sc.getKeyValue().entrySet()) {
gen.writeObjectField(keyValueEntry.getKey(), keyValueEntry.getValue());
}
gen.writeEndObject();
}
}
calling Jackson's mapper is done in the usual manner:
:
public static void main(String[] args)
{
Map<String, Object> keyValue = new HashMap<>();
keyValue.put("key1", "value1");
keyValue.put("key2", "value2");
keyValue.put("key3", new Integer(10));
SomeClass sc = new SomeClass("id1", keyValue);
try {
new ObjectMapper().writeValue(System.out, sc);
} catch (IOException e) {
e.printStackTrace();
}
}
output:
{"id":"id1","key1":"value1","key2":"value2","key3":10}

How to map a dynamic JSON property to a fixed POJO field

I have some json that i want to parse into pojo
{
"groups": [
{
"g1": [
1,2,5,6,7
]
},
{
"g2": [
2,3,48,79
]
}
]
}
Of course, g1 and g2 are the identifiers, so what i would imagine as pojos would be sth like
class Container {
List<Group> groups;
}
class Group {
String id;
List<Integer> values;
}
So it boils down to this question: How to use jackson to map a json-property to the pojo?
This kind of structure can be parsed using a custom deserializer added with the JsonDeserialize annotation.
POJOs
public static class Container {
private List<Group> groups;
public List<Group> getGroups() {
return groups;
}
public void setGroups(List<Group> groups) {
this.groups = groups;
}
#Override
public String toString() {
return String.format("Container [groups=%s]", groups);
}
}
#JsonDeserialize(using=CustomDeserializer.class)
public static class Group {
String id;
List<Integer> values;
#Override
public String toString() {
return String.format("Group [id=%s, values=%s]", id, values);
}
}
Deserializer, note use of ObjectMapper.readTree rather than using the low level JsonParser API...
public static class CustomDeserializer extends JsonDeserializer<Group> {
#Override
public Group deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
Group group = new Group();
ObjectNode objectNode = new ObjectMapper().readTree(jp);
// assume only a single field...
Entry<String, JsonNode> field = objectNode.fields().next();
group.id = field.getKey();
// there might be a nicer way to do this...
group.values = new ArrayList<Integer>();
for (JsonNode node : ((ArrayNode)field.getValue())) {
group.values.add(node.asInt());
}
return group;
}
}
Test
public static void main(String[] args) throws Exception {
String json = "{\"groups\": [{\"g1\":[1,2,5,6,7]},{\"g2\": [2,3,48,79]}]}";
JsonFactory f = new JsonFactory();
JsonParser jp = f.createParser(json);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(jp, Container.class));
}
Output
Container [groups=[Group [id=g1, values=[1, 2, 5, 6, 7]], Group [id=g2, values=[2, 3, 48, 79]]]]

Categories