I need to create a custom serializer that conditionally skips fields.
In contrast to the case described in
Skip objects conditionally when serializing with jackson my class contains a POJO member.
A PersonalInfo has a Address as member. In case the Address is hidden the resulting JSON still has the "address" tag, but without value.
I could not figure out how to fix this.
Creating a custom serializer on the ObjectMapper (see 3. at http://www.baeldung.com/jackson-custom-serialization) leads to the exact same result.
Here is the adapted code from the referenced question that shows the problem:
public class JacksonHide {
#JsonIgnoreProperties("hidden")
public static interface IHideable {
boolean isHidden();
}
public static class Address implements IHideable {
public final String city;
public final String street;
public final boolean hidden;
public Address(String city, String street, boolean hidden) {
this.city = city;
this.street = street;
this.hidden = hidden;
}
#Override
public boolean isHidden() {
return hidden;
}
}
public static class PersonalInfo implements IHideable {
public final String name;
public final int age;
public final Address address;
public final boolean hidden;
public PersonalInfo(String name, int age, Address address, boolean hidden) {
this.name = name;
this.age = age;
this.address = address;
this.hidden = hidden;
}
#Override
public boolean isHidden() {
return hidden;
}
}
private static class MyBeanSerializerModifier extends BeanSerializerModifier {
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (IHideable.class.isAssignableFrom(beanDesc.getBeanClass())) {
return new MyIHideableJsonSerializer((JsonSerializer<IHideable>) serializer);
}
return super.modifySerializer(config, beanDesc, serializer);
}
private static class MyIHideableJsonSerializer extends JsonSerializer<IHideable> {
private final JsonSerializer<IHideable> serializer;
public MyIHideableJsonSerializer(JsonSerializer<IHideable> serializer) {
this.serializer = serializer;
}
#Override
public void serialize(IHideable value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
if (!value.isHidden()) {
serializer.serialize(value, jgen, provider);
}
}
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setSerializerModifier(new MyBeanSerializerModifier());
mapper.registerModule(module);
PersonalInfo p1 = new PersonalInfo("John", 30, new Address("A", "B", false), false);
PersonalInfo p2 = new PersonalInfo("Ivan", 20, new Address("C", "D", true), true);
PersonalInfo p3 = new PersonalInfo("Mary", 40, new Address("C", "D", true), false);
Address a1 = new Address("A", "B", false);
Address a2 = new Address("C", "D", true);
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(Arrays.asList(p1, p2, p3, a1, a2)));
}
}
UPDATE:
Thanks to the feedback I have now a version based on #JSONFilter that gives me at least valid JSON. Unfortunately the nodes are still there, but are empty now ({}). How can I completely get rid of them?
public class JacksonFilterHide {
#JsonFilter("HiddenFilter")
#JsonIgnoreProperties("hidden")
public static interface IHideable {
boolean isHidden();
}
public static class Address implements IHideable {
public final String city;
public final String street;
public final boolean hidden;
public Address(String city, String street, boolean hidden) {
this.city = city;
this.street = street;
this.hidden = hidden;
}
#Override
public boolean isHidden() {
return hidden;
}
}
public static class PersonalInfo implements IHideable {
public final String name;
public final int age;
public final Address address;
public final boolean hidden;
public PersonalInfo(String name, int age, Address address, boolean hidden) {
this.name = name;
this.age = age;
this.address = address;
this.hidden = hidden;
}
#Override
public boolean isHidden() {
return hidden;
}
}
static final PropertyFilter hiddenFilter = new SimpleBeanPropertyFilter() {
#Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception {
if (include(writer)) {
if (pojo instanceof IHideable && ((IHideable) pojo).isHidden()) {
return;
} else {
writer.serializeAsField(pojo, jgen, provider);
return;
}
} else if (!jgen.canOmitFields()) { // since 2.3
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
#Override
protected boolean include(BeanPropertyWriter writer) {
return true;
}
#Override
protected boolean include(PropertyWriter writer) {
return true;
}
};
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
// ObjectMapper mapper = UserInteractionModel.getMapper();
FilterProvider filters = new SimpleFilterProvider().addFilter("HiddenFilter", hiddenFilter);
mapper.setFilters(filters);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
PersonalInfo p1 = new PersonalInfo("John", 30, new Address("A", "B", false), false);
PersonalInfo p2 = new PersonalInfo("Ivan", 20, new Address("C", "D", true), true);
PersonalInfo p3 = new PersonalInfo("Mary", 40, new Address("C", "D", true), false);
Address a1 = new Address("A", "B", false);
Address a2 = new Address("C", "D", true);
System.out.println(mapper.writeValueAsString(Arrays.asList(p1, p2, p3, a1, a2)));
}
}
Output now is:
[ { "name" : "John", "age" : 30, "address" : {
"city" : "A",
"street" : "B" } }, { }, { "name" : "Mary", "age" : 40, "address" : { } }, { "city" : "A", "street" : "B" }, { } ]
Expected:
[ { "name" : "John", "age" : 30, "address" : {
"city" : "A",
"street" : "B" } }, { "name" : "Mary", "age" : 40, }, { "city" : "A", "street" : "B" } ]
Update2
Temporary fix by traversing the tree and removing empty nodes. Ugly, but works for now. Still looking for a better answer.
private void removeEmptyNodes(JSONObject json) {
Iterator<String> iter = json.keys();
while (iter.hasNext()) {
String key = iter.next();
JSONObject node;
try {
node = json.getJSONObject(key);
} catch (JSONException e) {
continue;
}
if (node.length() == 0) {
iter.remove();
} else {
removeEmptyNodes(node);
}
}
}
Solution inspired by this question: How do I remove empty json nodes in Java with Jackson?
Your serializer is broken: it CAN NOT choose to NOT WRITE a value, if requested. In case of writing a property value, caller has already written out property name, so not writing the value will indeed break output.
This either results in an exception being thrown (ideally), or broken output (less ideally); regardless, JsonSerializer is not allowed to try to decide whether value is being written.
To exclude a property being serialized, your valid choices include:
Static property annotations like #JsonIgnore and #JsonIgnoreProperties that always exclude specific named property
Static annotation #JsonInclude that bases inclusion on type of value (no nulls, no absents, no empty values)
Static definition but dynamic choice of #JsonView to use (sets of properties you can dynamically exclude by associating with a view)
Dynamic #JsonFilter
Custom serializer for type that contains property
Related
I have gone through the threads from SOF which talks about getting nested JSON using GSON. Link 1 Link 2. My JSON file is as shown below
{
"Employee_1": {
"ZipCode": 560072,
"Age": 50,
"Place": "Hawaii",
"isDeveloper": true,
"Name": "Mary"
},
"Employee_2": {
"ZipCode": 560072,
"Age": 80,
"Place": "Texas",
"isDeveloper": true,
"Name": "Jon"
}
}
my classes are as shown below
public class Staff {
String Employee_1 ;
}
class addnlInfo{
String Name;
String Place;
int Age;
int Zipcode;
boolean isDeveloper;
}
The deserializer class which I built is as shown below
class MyDeserializer implements JsonDeserializer<addnlInfo>{
public addnlInfo deserialize1(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException
{
// Get the "content" element from the parsed JSON
JsonElement content = je.getAsJsonObject().get("Employee_1");
// Deserialize it. You use a new instance of Gson to avoid infinite recursion
// to this deserializer
return new Gson().fromJson(content, addnlInfo.class);
}
#Override
public TokenMetaInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
// TODO Auto-generated method stub
return null;
}
The main file
Gson gson = new GsonBuilder()
.registerTypeAdapter(addnlInfo.class, new MyDeserializer())
.create();
String jsonObject= gson.toJson(parserJSON);
addnlInfo info= gson.fromJson(jsonObject, addnlInfo .class);
System.out.println(info.Age + "\n" + info.isDeveloper + "\n" + info.Name + "\n" + info.Place);
Staff parentNode = gson.fromJson(jsonObject, Staff.class);
System.out.println(parentNode.Employee_1);
The problem:
My Subparent element (e.g. 'Employee_1') keeps changing. Do I have to construct multiple deserializers?
Also, I get "Expected a string but was BEGIN_OBJECT" which I understand as we use nestedJSON.
I am not sure how your classes translate to your JSON, but you are making this too complex.
I renamed fields and class names to adhere to Java standards.
Main.java
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
public class Main {
public static void main(String[] args) {
Map<String, Staff> employees = new LinkedHashMap<String, Staff>();
employees.put("Employee_1", new Staff(new Info("Mary", "Hawaii", 50, 56072, true)));
employees.put("Employee_2", new Staff(new Info("Jon", "Texas", 80, 56072, true)));
String jsonString = new GsonBuilder().setPrettyPrinting().create().toJson(employees);
System.out.println("# SERIALIZED DATA:");
System.out.println(jsonString);
Type mapOfStaff = new TypeToken<Map<String, Staff>>() {}.getType();
Map<String, Staff> jsonObject = new Gson().fromJson(jsonString, mapOfStaff);
System.out.println("\n# DESERIALIZED DATA:");
for (Entry<String, Staff> entry : jsonObject.entrySet()) {
System.out.printf("%s => %s%n", entry.getKey(), entry.getValue());
}
}
}
Staff.java
public class Staff {
private Info info;
public Staff(Info info) {
this.info = info;
}
public Info getInfo() {
return info;
}
public void setInfo(Info info) {
this.info = info;
}
#Override
public String toString() {
return String.format("Staff [info=%s]", info);
}
}
Info.java
public class Info {
private String name;
private String place;
private int age;
private int zipcode;
private boolean developer;
public Info(String name, String place, int age, int zipcode, boolean developer) {
this.name = name;
this.place = place;
this.age = age;
this.zipcode = zipcode;
this.developer = developer;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPlace() {
return place;
}
public void setPlace(String place) {
this.place = place;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getZipcode() {
return zipcode;
}
public void setZipcode(int zipcode) {
this.zipcode = zipcode;
}
public boolean isDeveloper() {
return developer;
}
public void setDeveloper(boolean developer) {
this.developer = developer;
}
#Override
public String toString() {
return String.format(
"Info [name=%s, place=%s, age=%d, zipcode=%d, developer=%b]",
name, place, age, zipcode, developer
);
}
}
Output
# SERIALIZED DATA:
{
"Employee_1": {
"info": {
"name": "Mary",
"place": "Hawaii",
"age": 50,
"zipcode": 56072,
"developer": true
}
},
"Employee_2": {
"info": {
"name": "Jon",
"place": "Texas",
"age": 80,
"zipcode": 56072,
"developer": true
}
}
}
# DESERIALIZED DATA:
Employee_1 => Staff [info=Info [name=Mary, place=Hawaii, age=50, zipcode=56072, developer=true]]
Employee_2 => Staff [info=Info [name=Jon, place=Texas, age=80, zipcode=56072, developer=true]]
Given I have the following json:
{
"Company": {
"name": "cookieltd",
"type": "food",
"franchise_location": [
{
"location_type": "town",
"address_1": "5street"
},
{
"location_type": "village",
"address_1": "2road"
}
]
}
}
How can it be binded to the following object classes using Jackson?:
1) Company class
public class Company
{
String name, type;
List<Location> franchise_location = new ArrayList<Location>();
[getters and setters]
}
2) Location class
public class Location
{
String location_type, address_1;
[getters and setters]
}
I have done:
String content = [json above];
ObjectReader reader = mapper.reader(Company.class).withRootName("Company"); //read after the root name
Company company = reader.readValue(content);
but I am getting:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "franchise_location"
As far as I can tell, you are simply missing an appropriately named getter for the field franchise_location. It should be
public List<Location> getFranchise_location() {
return franchise_location;
}
(and the setter)
public void setFranchise_location(List<Location> franchise_location) {
this.franchise_location = franchise_location;
}
Alternatively, you can annotate your current getter or field with
#JsonProperty("franchise_location")
private List<Location> franchiseLocation = ...;
which helps to map JSON element names that don't really work with Java field name conventions.
The following works for me
public static void main(String[] args) throws Exception {
String json = "{ \"Company\": { \"name\": \"cookieltd\", \"type\": \"food\", \"franchise_location\": [ { \"location_type\": \"town\", \"address_1\": \"5street\" }, { \"location_type\": \"village\", \"address_1\": \"2road\" } ] } }";
ObjectMapper mapper = new ObjectMapper();
ObjectReader reader = mapper.reader(Company.class).withRootName(
"Company"); // read after the root name
Company company = reader.readValue(json);
System.out.println(company.getFranchise_location().get(0).getAddress_1());
}
public static class Company {
private String name;
private String type;
private List<Location> franchise_location = new ArrayList<Location>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<Location> getFranchise_location() {
return franchise_location;
}
public void setFranchise_location(List<Location> franchise_location) {
this.franchise_location = franchise_location;
}
}
public static class Location {
private String location_type;
private String address_1;
public String getLocation_type() {
return location_type;
}
public void setLocation_type(String location_type) {
this.location_type = location_type;
}
public String getAddress_1() {
return address_1;
}
public void setAddress_1(String address_1) {
this.address_1 = address_1;
}
}
and prints
5street
my solution for JSON is always GSON, you can do some research on that, as long as you have the correct structure of class according to the JSON, it can automatically transfer from JSON to object:
Company company = gson.fromJson(json, Company.class);
GSON is so smart to do the convertion thing!
enjoy GSON !
I have created a json which have a root node with couple of child nodes using java now i have a requirement that the child node under the root may also have some children.But i am unable to do that.Here is what i have done so far....
class Entry {
private String name;
public String getChildren() {
return name;
}
public void setChildren(String name) {
this.name = name;
}
}
public class JsonApplication {
public static void main(String[] args) {
// TODO code application logic here
String arr[] = {"Culture", "Salary", "Work", "Effort"};
EntryListContainer entryListContainer = new EntryListContainer();
List<Entry> entryList1 = new ArrayList<>();
List<Entry> entryList2 = new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
Entry entry1 = new Entry();
entry1.setChildren(arr[i]);
entryList1.add(entry1);
entryList2.add(entry1);
/*Child nodes are created here and put into entryListContainer*/
entryListContainer.setEntryList1(entryList1);
entryListContainer.setEntryList1(entryList2);
}
/*Root node this will collapse and get back to Original position on click*/
entryListContainer.setName("Employee");
entryListContainer.setName("Culture");
Map<String, String> mapping = new HashMap<>();
mapping.put("entryList1", "name");
Gson gson = new GsonBuilder().serializeNulls().setFieldNamingStrategy(new DynamicFieldNamingStrategy(mapping)).create();
System.out.println(gson.toJson(entryListContainer));
}
}
class DynamicFieldNamingStrategy implements FieldNamingStrategy {
private Map<String, String> mapping;
public DynamicFieldNamingStrategy(Map<String, String> mapping) {
this.mapping = mapping;
}
#Override
public String translateName(Field field) {
String newName = mapping.get(field.getName());
if (newName != null) {
return newName;
}
return field.getName();
}
}
class EntryListContainer {
private List<Entry> children;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setEntryList1(List<Entry> entryList1) {
this.children = entryList1;
}
public List<Entry> getEntryList1() {
return children;
}
}
This is the json output i am getting
{
"children": [
{
"name":"Culture"
},
{
"name":"Salary"
},
{
"name":"Work"
},
{
"name":"Effort"
}
],
"name":"Employee"
}
But i need
{
"name":"Culture",
"children":[
{
"name":"Culture"
},
{
"name":"Salary"
},
{
"name":"Work"
},
{
"name":"Effort"
}
],
"name":"Work",
"children" : [
{
"name":"Culture"
},
{
"name":"Work"
}
]
}
I'm a bit confused by your code, but something is clear to me: what you want to get. So starting from scratch I created some code you can copy&run to see how you can get your desired JSON.
Probably the order of elements is important for you (pay attention that in JSON object order of keys is not important -is a map!-), so I edited some code that is not pure Gson way of doing things but that creates exactly your example.
package stackoverflow.questions;
import java.lang.reflect.Field;
import java.util.*;
import com.google.gson.*;
public class JsonApplication {
public static class EntryListContainer {
public List<Entry> children = new ArrayList<Entry>();
public Entry name;
}
public static class Entry {
private String name;
public Entry(String name) {
this.name = name;
}
}
public static void main(String[] args) {
EntryListContainer elc1 = new EntryListContainer();
elc1.name = new Entry("Culture");
elc1.children.add(new Entry("Salary"));
elc1.children.add(new Entry("Work"));
elc1.children.add(new Entry("Effort"));
EntryListContainer elc2 = new EntryListContainer();
elc2.name = new Entry("Work");
elc2.children.add(new Entry("Culture"));
elc2.children.add(new Entry("Work"));
ArrayList<EntryListContainer> al = new ArrayList<EntryListContainer>();
Gson g = new Gson();
al.add(elc1);
al.add(elc2);
StringBuilder sb = new StringBuilder("{");
for (EntryListContainer elc : al) {
sb.append(g.toJson(elc.name).replace("{", "").replace("}", ""));
sb.append(",");
sb.append(g.toJson(elc.children));
sb.append(",");
}
String partialJson = sb.toString();
if (al.size() > 1) {
int c = partialJson.lastIndexOf(",");
partialJson = partialJson.substring(0, c);
}
String finalJson = partialJson + "}";
System.out.println(finalJson);
}
}
and this is the execution:
{"name":"Culture",[{"name":"Salary"},{"name":"Work"},{"name":"Effort"}],"name":"Work",[{"name":"Culture"},{"name":"Work"}]}
I am a beginner of Jackson. How can I create a JSON message like this using Java?
{
"name": "John",
"age": "40",
"family": {
"parents_name": [
"David",
"Susan"
],
"children": "yes",
"children_names": [
"Peter",
"Mary"
]
}
}
Create a Person class in Java, with properties such as getName(), getAge() and so on. Then Jackson can create that JSON for you automatically, from your Person object.
The easiest way to do this for a beginner is to eliminate unnecessary nesting and rely on Jackson's default object binding.
You would create a class like this:
public class Person {
private String name;
private int age;
private List<String> parentNames;
private List<String> childrenNames;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<String> getParentNames() {
return parentNames;
}
public void setParentNames(List<String> parentNames) {
this.parentNames = parentNames;
}
public List<String> getChildrenNames() {
return childrenNames;
}
public void setChildrenNames(List<String> childrenNames) {
this.childrenNames = childrenNames;
}
}
Then you can instantiate a Person from JSON like this:
Person p = ObjectMapper.readValue(jsonString, Person.class);
Note that the JSON you have in your example won't work with this object for three reasons:
The Person class has no Family object. I felt that adds unnecessary complexity. If you want that, create a separate Family class, and Person would contain a Family member (no pun intended).
I don't have a boolean for children because that can be deduced from the length of the childrenNames list.
The JSON will need to have childrenNames and parentNames rather than children_names and parents_name. If you want those, add #JsonProperty with the desired property names on the getters and setters for those values.
I gather from your comments to Vidya's solution that your looking for more flexibility than you get can get with the default binding.
Jackson allows you to create your own custom serializer. For example:
public class Person {
private String name;
private int age;
private List<String> parentsName;
private List<String> childrenNames;
public Person(String name, List<String> parentsName) {
this(name, parentsName, -1, Collections.<String>emptyList());
}
public Person(String name, List<String> parentsName, int age) {
this(name, parentsName, age, Collections.<String>emptyList());
}
public Person(String name, List<String> parentsName, int age, List<String> childrenNames) {
this.name = name;
this.age = age;
this.parentsName = parentsName;
this.childrenNames = childrenNames;
}
private void serialize(JsonGenerator generator, SerializerProvider arg2) throws IOException {
generator.writeStartObject();
generator.writeObjectField("name", name);
if (age >= 0)
generator.writeNumberField("age", age);
// start family subset
generator.writeObjectFieldStart("family");
generator.writeArrayFieldStart("parents_name");
for (String parent : parentsName) {
generator.writeObject(parent);
}
generator.writeEndArray();
generator.writeObjectField("children", (childrenNames.isEmpty() ? "no" : "yes"));
generator.writeArrayFieldStart("children_names");
for (String child : childrenNames) {
generator.writeObject(child);
}
generator.writeEndArray();
generator.writeEndObject();
// end family subset
generator.writeEndObject();
}
public static JsonSerializer<Person> createJsonSerializer() {
return new JsonSerializer<Person>() {
#Override
public void serialize(Person me, JsonGenerator generator, SerializerProvider arg2) throws IOException, JsonProcessingException {
me.serialize(generator, arg2);
}
};
}
public static void main(String[] args) throws IOException {
List<String> parentsName = Arrays.<String>asList("David", "Susan");
List<String> childrenNames = Arrays.<String>asList("Peter", "Mary");
Person person = new Person("John", parentsName, 40, childrenNames);
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("PersonModule", new Version(1, 0, 0, null));
simpleModule.addSerializer(Person.class, Person.createJsonSerializer());
// pretty output for debugging
mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
mapper.registerModule(simpleModule);
System.out.println("Person json: ");
System.out.println(mapper.writeValueAsString(person));
}
}
This gives you increased flexibility in two ways:
You can apply conditional logic in serialization
You can have multiple custom serializers
The downsides are fairly obvious
More complicated
More time to implement. The default bindings were almost free. This solution is not.
I would like deserialize my custom serialized objects. My objects are basically consisting a simple Pair implementation.
class School{
Integer id;
String schoolName;
}
class Student{
Integer id;
Integer schoolId;
String studentName;
}
#JsonSerialize(using=PairSerializer.class)
public class Pair<V,K>{
V v;
K k;
}
Here is the result
[
{
"v":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
},
"k":{
"id":3,
"schoolName":"School 3"
}
},
{
"v":{
"id":2,
"schoolId":3,
"studentName":"C. Koc"
},
"k":{
"id":3,
"schoolName":"School 3"
}
}
]
v and k as field name in json is pretty ugly. That is why I have written a custom serializer as this:
#Override
public void serialize(Pair pair, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeObjectField(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,pair.getK().getClass().getSimpleName() ), pair.getK());
jsonGenerator.writeObjectField(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,pair.getV().getClass().getSimpleName() ), pair.getV());
jsonGenerator.writeEndObject();
}
The result is exactly what I want. v and k field names are replaced by their class names.
[
{
"school":{
"id":3,
"schoolName":"School 3"
},
"student":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
}
},
{
"school":{
"id":3,
"schoolName":"School 3"
},
"student":{
"id":2,
"schoolId":3,
"studentName":"C. Koc"
}
}
]
Here is the my question. How can I deserialize my json string to List<Pair<V, K> ? The real problem is that V and K are depends on the deserialized context it might vary as Student, School or another pair implementation.
public class PairDeserializer extends JsonDeserializer<Pair> {
public PairDeserializer() {
}
#Override
public Pair deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// I need to Deserialized generic type information of Pair
}
}
I think, you should create your own PropertyNamingStrategy. For example see my simple implementation:
class MapTransformNamingStrategy extends LowerCaseWithUnderscoresStrategy {
private static final long serialVersionUID = 1L;
private Map<String, String> mapping;
public MapTransformNamingStrategy(Map<String, String> mapping) {
this.mapping = mapping;
}
#Override
public String translate(String property) {
if (mapping.containsKey(property)) {
return mapping.get(property);
}
return property;
}
}
Now you can use it in this way:
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "student");
mapping.put("v", "school");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(new MapTransformNamingStrategy(mapping));
//etc
Example JSON output:
{ "school" : { "id" : 1,
"schoolName" : "The Best School in the world"
},
"student" : { "id" : 1,
"schoolId" : 1,
"studentName" : "Arnold Shwarz"
}
}
EDIT
Because my answer is not clear for everyone I present full example source code which serialize Java POJO objects into JSON and "vice versa".
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy;
public class JacksonProgram {
#SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
List<Pair<Student, School>> pairs = createDataForSerialization();
Map<String, String> mapping = createSchoolStudentMapping();
JsonConverter jsonConverter = new JsonConverter(mapping);
String json = jsonConverter.toJson(pairs);
System.out.println("JSON which represents list of pairs:");
System.out.println(json);
List<Pair<Student, School>> value = jsonConverter.fromJson(json, List.class);
System.out.println("----");
System.out.println("Deserialized version:");
System.out.println(value);
}
private static Map<String, String> createSchoolStudentMapping() {
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "student");
mapping.put("v", "school");
return mapping;
}
private static List<Pair<Student, School>> createDataForSerialization() {
List<Pair<Student, School>> pairs = new ArrayList<Pair<Student, School>>();
pairs.add(new Pair<Student, School>(new Student(1, 3, "O. Bas"), new School(3, "School 3")));
pairs.add(new Pair<Student, School>(new Student(2, 4, "C. Koc"), new School(4, "School 4")));
return pairs;
}
}
class JsonConverter {
private Map<String, String> mapping;
private ObjectMapper objectMapper;
private JsonFactory jsonFactory;
public JsonConverter(Map<String, String> mapping) {
this.mapping = mapping;
initJsonObjects();
}
private void initJsonObjects() {
objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(new MapTransformNamingStrategy(mapping));
jsonFactory = new JsonFactory();
}
public String toJson(Object object) throws Exception {
StringWriter stringWriter = new StringWriter();
JsonGenerator jsonGenerator = jsonFactory.createGenerator(stringWriter);
objectMapper.writeValue(jsonGenerator, object);
return stringWriter.toString();
}
public <T> T fromJson(String json, Class<T> expectedType) throws Exception {
JsonParser jsonParser = jsonFactory.createJsonParser(json);
return objectMapper.readValue(jsonParser, expectedType);
}
}
class MapTransformNamingStrategy extends LowerCaseWithUnderscoresStrategy {
private static final long serialVersionUID = 1L;
private Map<String, String> mapping;
public MapTransformNamingStrategy(Map<String, String> mapping) {
this.mapping = mapping;
}
#Override
public String translate(String property) {
if (mapping.containsKey(property)) {
return mapping.get(property);
}
return property;
}
}
class School {
private Integer id;
private String schoolName;
public School() {
}
public School(Integer id, String schoolName) {
this.id = id;
this.schoolName = schoolName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
#Override
public String toString() {
return "School [id=" + id + ", schoolName=" + schoolName + "]";
}
}
class Student {
private Integer id;
private Integer schoolId;
private String studentName;
public Student() {
}
public Student(Integer id, Integer schoolId, String studentName) {
this.id = id;
this.schoolId = schoolId;
this.studentName = studentName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getSchoolId() {
return schoolId;
}
public void setSchoolId(Integer schoolId) {
this.schoolId = schoolId;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
#Override
public String toString() {
return "Student [id=" + id + ", schoolId=" + schoolId + ", studentName=" + studentName
+ "]";
}
}
class Pair<V, K> {
private V v;
private K k;
public Pair() {
}
public Pair(V v, K k) {
this.v = v;
this.k = k;
}
public V getV() {
return v;
}
public void setV(V v) {
this.v = v;
}
public K getK() {
return k;
}
public void setK(K k) {
this.k = k;
}
#Override
public String toString() {
return "Pair [v=" + v + ", k=" + k + "]";
}
}
The full output log:
JSON which represents list of pairs:
[{"school":{"id":1,"schoolId":3,"studentName":"O. Bas"},"student":{"id":3,"schoolName":"School 3"}},{"school":{"id":2,"schoolId":4,"studentName":"C. Koc"},"student":{"id":4,"schoolName":"School 4"}}]
----
Deserialized version:
[{school={id=1, schoolId=3, studentName=O. Bas}, student={id=3, schoolName=School 3}}, {school={id=2, schoolId=4, studentName=C. Koc}, student={id=4, schoolName=School 4}}]
Because the output JSON is not formatted I present it in more understandable version:
[
{
"school":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
},
"student":{
"id":3,
"schoolName":"School 3"
}
},
{
"school":{
"id":2,
"schoolId":4,
"studentName":"C. Koc"
},
"student":{
"id":4,
"schoolName":"School 4"
}
}
]
As you can see, we create new JsonConverter object with definition of mapping between Pair property names and which names we want to see in JSON string representation. Now if you have for example Pair<School, Room> you can create mapping Map in this way:
private static Map<String, String> createSchoolRoomMapping() {
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "school");
mapping.put("v", "room");
return mapping;
}
I was going for an answer with some annotation (JsonTypeInfo and JsonUnwrapped), but those two don't work well together apparently (see this issue). That would of handled both the serialization and deserialization part of your problem, without relying on custom de/serializer. Instead, you'll need a custom deserializer, which does something along those line:
class PairDeserializer extends JsonDeserializer<Pair>{
static Map<String, Class> MAPPINGS = new HashMap<String, Class>();
#Override
public Pair deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Object key = deserializeField(jp);
Object value = deserializeField(jp);
Pair pair = new Pair();
pair.k = key;
pair.v = value;
jp.nextToken();
return pair;
}
private Object deserializeField(JsonParser jp) throws IOException, JsonParseException, JsonProcessingException {
jp.nextValue();
String className = jp.getCurrentName();
return jp.readValueAs(MAPPINGS.get(className));
}
}
Then you only need to register the mappings you need