json jackson unusual map serialization - java

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}

Related

Jackson JSON Serialization without field name

I have a JAVA POJO which has many fields. One of the fields is Map<String, Object> for which I am using the Custom JsonSerializer as it can have many type of Objects. All I want to know is how can I avoid the Serialization of the fieldname only for this Map<String,Object> field. For all other fields in POJO, I would like to have the field name but only for this, I want to remove it.
As of now when use Jackson searlizer then I get the following output:
{
"isA" : "Human",
"name" : "Batman",
"age" : "2008",
"others" : {
"key1" : "value1",
"key2" : {
"key3" : "value3"
},
"key5" : {
"key4" : "One",
"key4" : "Two"
}
}
}
I want to get the following output: (All I want to do is remove the Map<String,Object> field name but keep its children.)
{
"isA" : "Human",
"name" : "Batman",
"age" : "2008",
"key1" : "value1",
"key2" : {
"key3" : "value3"
},
"key5" : {
"key4" : "One",
"key4" : "Two"
}
}
Following is my Human.class POJO which is used by ObjectMapper:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#Getter
#Setter
#NoArgsConstructor
#ToString
class Human {
private String isA;
private String name;
private String age;
#JsonSerialize(using = MyCustomSearlize.class)
private Map<String, Object> others = new HashMap<>();
}
Following is my Custom searlizer which is used by MAP during searlization:
class MyCustomSearlize extends JsonSerializer<Map<String, Object>> {
private static final ObjectMapper mapper = new ObjectMapper();
#Override
public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
recusiveSerializer(value, gen, serializers);
gen.writeEndObject();
}
public void recusiveSerializer(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
for (Map.Entry<String, Object> extension : value.entrySet()) {
if (extension.getValue() instanceof Map) {
//If instance is MAP then call the recursive method
gen.writeFieldName(extension.getKey());
gen.writeStartObject();
recusiveSerializer((Map) extension.getValue(), gen, serializers);
gen.writeEndObject();
} else if (extension.getValue() instanceof String) {
//If instance is String directly add it to the JSON
gen.writeStringField(extension.getKey(), (String) extension.getValue());
} else if (extension.getValue() instanceof ArrayList) {
//If instance if ArrayList then loop over it and add it to the JSON after calling recursive method
for (Object dupItems : (ArrayList<Object>) extension.getValue()) {
if (dupItems instanceof Map) {
gen.writeFieldName(extension.getKey());
gen.writeStartObject();
recusiveSerializer((Map) dupItems, gen, serializers);
gen.writeEndObject();
} else {
gen.writeStringField(extension.getKey(), (String) dupItems);
}
}
}
}
}
}
Following is my Main class:
public class Main {
public static void main(String[] args) throws JsonProcessingException {
Human person = new Human();
person.setName("Batman");
person.setAge("2008");
Map<String, Object> others = new HashMap<>();
others.put("key1", "value1");
Map<String, Object> complex = new HashMap<>();
complex.put("key3", "value3");
others.put("key2", complex);
Map<String, Object> complex2 = new HashMap<>();
List<String> dup = new ArrayList<>();
dup.add("One");
dup.add("Two");
complex2.put("key4", dup);
others.put("key5", complex2);
person.setOthers(others);
final ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
objectMapper.registerModule(simpleModule);
final String jsonEvent = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(person);
System.out.println(jsonEvent);
}
}
Following things I tried:
I tried to add #JsonValue on the Map but this will remove all my other values (name,age, isA, etc.)
I tried the #JsonAnyGetter this works for Map and String but does not work for ArrayList as I want. I am handling the ArrayList bit differently in my application as a part of my requirement.
Is there a way to may it work with #JsonSerialize and #JsonAnyGetter because I am unable to use both together.
Can someone please help in solving this issue? Please guide me to appropriate documentation or workaround thanks a lot.
From the wiki page it sounds like the #JsonUnwrapped annotation should do what you want.
#JsonUnwrapped: property annotation used to define that value should be "unwrapped" when serialized (and wrapped again when deserializing), resulting in flattening of data structure, compared to POJO structure.
The Javadoc for the class also has an example that looks appropriate.
As mentioned in another answer #JsonUnwrapped might work but I used the following approach to get it working. Posting here as it can be helpful to someone in the future:
I added the #JsonAnyGetter and #JsonSearlize on the Getter method of Map and got it to work.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#Getter
#Setter
#NoArgsConstructor
#ToString
class Human {
private String isA;
private String name;
private String age;
#JsonIgnore
private Map<String, Object> others = new HashMap<>();
#JsonAnyGetter
#JsonSerialize(using = MyCustomSearlize.class)
public Map<String,Object> getOther(){
return others;
}
}
In the MyCustomSearlize class code I removed the start and end object
#Override
public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
recusiveSerializer(value, gen, serializers);
}

Serialize nested objects with jackson

I want to have an output like
{ Orga1: [ dep1, dep2], Orga2: [dep88, dep99], ...}
but somehow I fail to get it properly done.
I have the following structure:
#JsonSerialize(using = OrganisationSerializer.class)
public class Organisation {
String name;
private HashMap<String, Department> lstDepartments = new HashMap<>();
public List<Department> getList() {
return lstDepartments.values().stream().collect(Collectors.toList());
}
}
with the nested class
#JsonSerialize(using = DepartmentSerializer.class)
public class Department {
String name;
HashMap<String, Role4Filter> lstRole = new HashMap<>();
public List<Role4Filter> getList() {
return lstRole.values().stream().collect(Collectors.toList());
}
...
}
The major problem is that the HashMap needs to be transferred into a List which needs to be serialized. But somehow I fail to convert into a JSON properly.
My approach with
public class OrganisationSerializer extends JsonSerializer<Organisation> {
#Override
public void serialize(Organisation value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeArrayFieldStart(value.name);
for (final Department item : value.getList()) {
gen.writeObject(item);
}
gen.writeEndArray();
gen.writeEndObject();
}
fails with the exception that
com.fasterxml.jackson.databind.JsonMappingException: org..Department cannot be cast to org..Organisation
Any ideas? Or is there some other annotation possible (beside the serializer?)

Jackson catch unrecognized field in a map

I'm using Jackson in a java Rest Api to handle request params.
My Bean class :
public class ZoneModifBeanParam extends ModifBeanParam<Zone> {
#FormParam("type")
private String type;
#FormParam("geometry")
private Geometry geometry;
#FormParam("name")
private String name;
...
My API interface :
#POST
#Consumes("application/json")
#Produces("application/json; subtype=geojson")
#ApiOperation(value = "Create a zone", notes = "To create a zone")
public Response createZone(ZoneModifBeanParam zoneParam) {
...
This Works fine but I need to receive other params that aren't specified by my Bean in a Map.
Example :
{
"geometry": {...},
"name": "A circle name",
"type": "4",
"hello": true
}
By receiving this I need to store in a Map (named unrecognizedFields and declared in my bean) the couple ("hello", true).
Is there any annotation or object allowing this?
Just use #JsonAnySetter. That's what it's made for. Here is a test case
public class JacksonTest {
public static class Bean {
private String name;
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
private Map<String, Object> unrecognizedFields = new HashMap<>();
#JsonAnyGetter
public Map<String, Object> getUnrecognizedFields() {
return this.unrecognizedFields;
}
#JsonAnySetter
public void setUnrecognizedFields(String key, Object value) {
this.unrecognizedFields.put(key, value);
}
}
private final String json
= "{\"name\":\"paul\",\"age\":600,\"nickname\":\"peeskillet\"}";
private final ObjectMapper mapper = new ObjectMapper();
#Test
public void testDeserialization() throws Exception {
final Bean bean = mapper.readValue(json, Bean.class);
final Map<String, Object> unrecognizedFields = bean.getUnrecognizedFields();
assertEquals("paul", bean.getName());
assertEquals(600, unrecognizedFields.get("age"));
assertEquals("peeskillet", unrecognizedFields.get("nickname"));
}
}
The #JsonAnyGetter is used on the serialization side. When you serialize the bean, you will not see the unrecognizedFields in the JSON. Instead all the properties in the map will be serialized as top level properties in the JSON.
You may be able to ignore the unrecognized fields safely by configuring the ObjectMapper, however to specifically put them as key-value pairs of a Map field, you'll need your own de-serializer.
Here's a (heavily simplified) example:
Given your POJO...
#JsonDeserialize(using=MyDeserializer.class)
class Foo {
// no encapsulation for simplicity
public String name;
public int value;
public Map<Object, Object> unrecognized;
}
... and your custom de-serializer...
class MyDeserializer extends JsonDeserializer<Foo> {
#Override
public Foo deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// new return object
Foo foo = new Foo();
// setting unrecognized container
Map<Object, Object> unrecognized = new HashMap<>();
foo.unrecognized = unrecognized;
// initializing parsing from root node
JsonNode node = p.getCodec().readTree(p);
// iterating node fields
Iterator<Entry<String, JsonNode>> it = node.fields();
while (it.hasNext()) {
Entry<String, JsonNode> child = it.next();
// assigning known fields
switch (child.getKey()) {
case "name": {
foo.name = child.getValue().asText();
break;
}
case "value": {
foo.value = child.getValue().asInt();
break;
}
// assigning unknown fields to map
default: {
foo.unrecognized.put(child.getKey(), child.getValue());
}
}
}
return foo;
}
}
Then, somewhere...
ObjectMapper om = new ObjectMapper();
Foo foo = om.readValue("{\"name\":\"foo\",\"value\":42,\"blah\":true}", Foo.class);
System.out.println(foo.unrecognized);
Output
{blah=true}

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

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!

Working with multiple JSON objects in the same file with unknown key names using Jackson

Working on building the model for an application dealing with physical buildings.
Ideally, we'd want something like this:
City has multiple Offices, which have multiple Rooms, which have properties.
We're using jackson to parse the JSON payload received from the API datasource, and it ends up looking a bit differently than the examples I've seen.
The format we're getting is:
{
"CityName1":
{ "OfficeName1":
[
{"name": RoomName1, "RoomProperty2": RoomValue1},
{"name": RoomName2, "RoomProperty2": RoomValue2}
]
},
{ "OfficeName2": [{...}]},
{ "OfficeNameX" : [{...}] },
"CityName2": {...},
"CityNameN": {...}}
Java classes:
public class City {
private Map<String, Object> additionalProperties = new HashMap<String, Object();
private List<Office> _offices = new ArrayList<Office>();
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value)
throws IOException {
_cityName = name;
String officeJson = _mapper.writeValueAsString(value);
StringBuilder sb = new StringBuilder(officeJson);
_offices.add(_mapper.readValue(officeJson, Office.class));
this.additionalProperties.put(name, value);
}
}
public class Office {
private String _officeName;
private static final ObjectMapper _mapper = new ObjectMapper();
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
private List<Room> _rooms = new ArrayList<Room>();
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value)
throws IOException {
_officeName = name;
String roomJson = _mapper.writeValueAsString(value);
Room[] rooms = _mapper.readValue(roomJson, Room[].class);
_rooms.addAll(Arrays.asList(rooms));
this.additionalProperties.put(name, value);
}
public List<Room> getRooms() {
return _rooms;
}
public void setRooms(List<Room> rooms) {
_rooms = rooms;
}
}
public class Room {
private static final String NAME = "name";
private static final String PROP_2 = "RoomProperty2";
#JsonProperty(PROP_2)
private String _propertyTwo;
#JsonProperty(NAME)
private String name;
#JsonProperty(PROP_2)
public String getPropertyTwo() {
return _propertyTwo;
}
#JsonProperty(PROP_2)
public void setPropertyTwo(String propTwo) {
_propertyTwo = propTwo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
So how would I go about parsing this with jackson ? Currently, I am using an #JsonAnySetter to grab the name, and saving that as the city or office name and then sending the value sent to JsonAnySetter to the appropriate nested class. The real issue comes with getting a list of Offices in the City. When using a mapper.readvalues(String, Office.class), I get returned an iterator of only the last office for each city. Any ideas guys?
Sorry if that seemed confusing! Would love to answer any questions I've created.
Thanks for the help!
I think the best solution is to write your own deserialiser here since your JSON document doesn't really map well to the class structure you want.
The solution below reads each city as a Map<String, List<Room>> and the collection of cities as a Map<String, City> and then create City and Office objects from these inside the deserialisers.
Room.java is the same as yours, here are the rest:
Cities.java:
#JsonDeserialize(using=CitiesDeserializer.class)
public class Cities implements Iterable<City> {
private final List<City> cities;
public Cities(final List<City> cities) {
this.cities = cities;
}
public Cities() {
this.cities = new ArrayList<>();
}
public List<City> getCities() {
return cities;
}
#Override
public Iterator<City> iterator() {
return cities.iterator();
}
}
CitiesDeserialiser.java:
public class CitiesDeserializer extends JsonDeserializer<Cities> {
private static final TypeReference<Map<String, City>> TYPE_REFERENCE = new TypeReference<Map<String, City>>() {};
#Override
public Cities deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
final Map<String, City> map = jp.readValueAs(TYPE_REFERENCE);
List<City> cities = new ArrayList<>();
for(Map.Entry<String, City> entry : map.entrySet()) {
City city = entry.getValue();
city.setName(entry.getKey());
cities.add(city);
}
return new Cities(cities);
}
}
City.java:
#JsonDeserialize(using=CityDeserialzer.class)
public class City {
private String name;
private List<Office> offices;
// Setters and getters
}
CityDeserializer.java:
public class CityDeserialzer extends JsonDeserializer<City> {
private static final TypeReference<Map<String, List<Room>>> TYPE_REFERENCE = new TypeReference<Map<String, List<Room>>>() {};
#Override
public City deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
final Map<String, List<Room>> map = jp.readValueAs(TYPE_REFERENCE);
List<Office> offices = new ArrayList<>();
for(Map.Entry<String, List<Room>> entry : map.entrySet()) {
Office office = new Office();
office.setName(entry.getKey());
office.setRooms(entry.getValue());
offices.add(office);
}
City city = new City();
city.setOffices(offices);
return city;
}
}
Office.java:
public class Office {
private String name;
private List<Room> rooms;
// Setters and getters
}
And here's a test to show that it works:
JSON:
{
"CityName1": {
"OfficeName1": [ {
"name": "RoomName1",
"RoomProperty2": "RoomValue1"
}, {
"name": "RoomName2",
"RoomProperty2": "RoomValue2"
} ],
"OfficeName2": [ {
"name": "RoomName3",
"RoomProperty2": "RoomValue3"
}, {
"name": "RoomName4",
"RoomProperty2": "RoomValue4"
} ]
},
"CityName2": {
"OfficeName3": [ {
"name": "RoomName5",
"RoomProperty2": "RoomValue5"
}, {
"name": "RoomName6",
"RoomProperty2": "RoomValue6"
} ],
"OfficeName4": [ {
"name": "RoomName7",
"RoomProperty2": "RoomValue7"
}, {
"name": "RoomName8",
"RoomProperty2": "RoomValue8"
} ]
}
}
Test.java:
public class Test {
public static void main(String[] args) {
String json = ...
ObjectMapper mapper = new ObjectMapper();
Cities cities = mapper.readValue(json, Cities.class);
for(City city : cities) {
System.out.println(city.getName());
for(Office office : city.getOffices()) {
System.out.println("\t" + office.getName());
for(Room room : office.getRooms()) {
System.out.println("\t\t" + room.getName());
System.out.println("\t\t\t" + room.getPropertyTwo());
}
}
}
}
}
Output:
CityName1
OfficeName1
RoomName1
RoomValue1
RoomName2
RoomValue2
OfficeName2
RoomName3
RoomValue3
RoomName4
RoomValue4
CityName2
OfficeName3
RoomName5
RoomValue5
RoomName6
RoomValue6
OfficeName4
RoomName7
RoomValue7
RoomName8
RoomValue8

Categories