Mapping some HashMap Key/Values to a POJO - java

I have a HashMap of about 300 Key/String Value pairs and a POJO with about 12 string attributes where the names match the key names.
I would like to know how to get the HashMap values into the POJO?
I made this start which uses relfection and a loop but wasn't sure how to dynamically construct the setter method name, and apparently reflection is a bad idea anyway...but FWIW:
public void writeToFile(Map<String, String> currentSale) throws IOException {
SaleExport saleExport = new SaleExport();
Field[] fields = saleExport.getClass().getDeclaredFields();
for (Field field : fields ) {
System.out.println(field.getName());
saleExport.set +field(saleExport.get(field));
I have used map struct once before but it does not appear to support HashMaps.
UPDATE
This answer looks similar to what I want to do but gave a stack error on fields that didn't map:
Exception in thread "Thread-2" java.lang.IllegalArgumentException: Unrecognized field "Physical" (class com.SaleExport), not marked as ignorable (6 known properties: "date", "city", "surname", "streetName", "salesNo", "salesSurname"])
at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.SalesExport["Physical"])
at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3738)
at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:3656)
at com.CSVExport.writeToFile(CSVExport.java:20)
at com.JFrameTest.writefiletoDB(JFrameTest.java:135)
at com.JFrameTest$FileWorkerThread.run(JFrameTest.java:947)
To ignore the errors I tried :
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
But then nothing got mapped.

If i have understood your question correctly - you want to put the value of map into the member variable of the Pojo based on key.
Try below approach.
Main Class as follows
package org.anuj.collections.map;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class ConverMapToPojo {
public static void main(String[] args) {
Map<String, String> map = getMap();
Set<String> keySet = map.keySet();
String fieldName = null;
Pojo pojo = new Pojo();
Field[] field = Pojo.class.getDeclaredFields();
for (Field f : field) {
fieldName = f.getName();
if (keySet.contains(fieldName)) {
pojo = setField(fieldName, map.get(fieldName), pojo);
}
}
System.out.println("fName = " + pojo.getfName());
System.out.println("lName = " + pojo.getlName());
}
private static Pojo setField(String fieldName, String value, Pojo pojo) {
switch (fieldName) {
case "fName":
pojo.setfName(value);
break;
case "lName":
pojo.setlName(value);
break;
}
return pojo;
}
private static Map<String, String> getMap() {
Map<String, String> map = new HashMap<String, String>();
map.put("fName", "stack");
map.put("lName", "overflow");
return map;
}
}
Pojo Class as follows -
public class Pojo {
private String fName;
private String lName;
public String getfName() {
return fName;
}
public void setfName(String fName) {
this.fName = fName;
}
public String getlName() {
return lName;
}
public void setlName(String lName) {
this.lName = lName;
}
}
The Result comes out to be
fName = stack
lName = overflow

Try using Dozer mapping to map HashMap to POJO.You can look at MapStruct too.

Hi I don't know if it's a good Idea but you could convert your map to a json and convert json to your POJO.
You could use gson.

You can inject something in a pojo member variable through e.g. a method like this. I do not say that it is a good way to do this, but it can be done like this.
import java.lang.reflect.Field;
import java.util.HashMap;
/**
* #author Ivo Woltring
*/
public class HashMapKeyStuff {
/**
* The most simple cdi like method.
*
* #param injectable the object you want to inject something in
* #param fieldname the fieldname to inject to
* #param value the value to assign to the fieldname
*/
public static void injectField(final Object injectable, final String fieldname, final Object value) {
try {
final Field field = injectable.getClass()
.getDeclaredField(fieldname);
final boolean origionalValue = field.isAccessible();
field.setAccessible(true);
field.set(injectable, value);
field.setAccessible(origionalValue);
} catch (final NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private void doIt() {
HashMap<String, String> foo = new HashMap<>();
foo.put("hello", "world");
foo.put("message", "You are great");
MyPOJO pojo = new MyPOJO();
for (final String key : foo.keySet()) {
injectField(pojo, key, foo.get(key));
}
System.out.println("pojo = " + pojo);
}
public static void main(String[] args) {
new HashMapKeyStuff().doIt();
}
}
class MyPOJO {
private String hello;
private String message;
public String getHello() {
return this.hello;
}
public String getMessage() {
return this.message;
}
#Override
public String toString() {
return "MyPOJO{" +
"hello='" + hello + '\'' +
", message='" + message + '\'' +
'}';
}
}

I think you had the right idea in your code. I understand your POJO to contain only a subset of the fields represented in the HashMap, so you just iterate through the fields, populating them from the HashMap as you find them.
public SaleExport toSaleExport(Map<String,String> currentSale) {
SaleExport saleExport=new SaleExport();
Field[] fields=SaleExport.class.getDeclaredFields();
for (Field field : fields) {
String name=field.getName();
if (currentSale.containsKey(name)) {
field.set(saleExport, currentSale.get(name));
}
}
return saleExport;
}

Related

Java Reflection set enum in model

I'm trying to update my DTO with Reflection. The problem is that some fields in my DTO are enums and I get an error that I can not set the enum field to String.
DTO:
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
#Table(name = "xxx")
public class Model {
#Id
#Column(name = "id")
private String runId;
#Column(name = "name")
private String name;
#Column(name = "status")
#Enumerated(EnumType.STRING)
private ExecutionStatus status;
}
Controller:
#PatchMapping(path = "/{id}", consumes = "application/json")
public ResponseEntity<Void> partialUpdateModel(#PathVariable String id, #RequestBody Map<Object, Object> fields)
throws Exception {
Optional<Model> model= service.getById(id);
if (model.isPresent()) {
fields.forEach((key, value) -> {
Field field = ReflectionUtils.findField(Model.class, (String) key);
field.setAccessible(true);
ReflectionUtils.setField(field, model.get(), value);
});
So when it comes to the enum field, the field can not be set. It says
cannot set ExecutionStatus to String
What you are trying to do is this:
model.status = "STATUS_1";
// incompatible types: java.lang.String cannot be converted to so.A.ExecutionStatus
What you apparently want to do is finding the enum constant of the specified enum type with the specified string. That's what the Enum.valueOf or YourEnum.valueOf method is doing.
Example:
enum ExecutionStatus {
STATUS_1,
STATUS_2,
}
static class Model {
public String runId;
public String name;
public ExecutionStatus status;
#Override
public String toString() {
return "Model{" +
"runId='" + runId + '\'' +
", name='" + name + '\'' +
", status=" + status +
'}';
}
}
public static void main(String[] args) {
Map<Object, Object> fields = Map.of(
"runId", "MyRunId",
"name", "MyName",
"status", "STATUS_1"
);
Model model = new Model();
fields.forEach((key, value) -> {
Field field = null;
try {
field = Model.class.getDeclaredField((String) key);
field.setAccessible(true);
if (field.getType().isEnum()) {
// First variant (YourEnum.valueOf(String)
Method valueOf = field.getType().getMethod("valueOf", String.class);
Object enumConstant = valueOf.invoke(null, value);
field.set(model, enumConstant);
// Alternative (Enum.valueOf(Class, String) (cast is safe due to isEnum)
field.set(model, Enum.valueOf((Class<Enum>) field.getType(), (String) value));
} else {
field.set(model, value);
}
} catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
});
System.out.println(model);
}
You can do like this, Also understand reflection is costly it involves types that are being dynamically resolved, i'd recommend writing setters or constructor instead
if ("ExecutionStatus".equalsIgnoreCase(field.getType().getSimpleName())) {
ReflectionUtils.setField(field, model.get(), ExecutionStatus.valueOf(value));
} else {
ReflectionUtils.setField(field, model.get(), value);
}
This is because your source map is of type <Object, Object>
What you try to do is to set a field of type ExecutionStatus reading a value of type Object. Types on your fields must match. First convert a value to ExecutionStatus then use the method .setField(..).

Java - set the field value to the correct type from string(reflection)

I have class like this:
public enum Type {
ONE, TWO
}
#Data
public class Car {
private String name;
private int year;
private Type type;
}
I have new object:
Car car = new Car();
And I have this data:
Map<String, String> data....
name - BMW
year - 2018
type - TWO
key and value - String
And I need set this values to object(except for reflection, I see no ways)
Field year = car.getClass().getDeclaredField("year");
year.setAccessible(true);
year.set(car, data.get("year"));//2018 as string
I get exception(differently and could not be I know):
Caused by: java.lang.IllegalArgumentException: Can not set int field com.example.mapper.Car.year to java.lang.String
Therefore, the question is - how do I correctly cast the value to the desired type to set in the field?
This is a simple example, because the real task is very long explained. If in short - I get a list of values (they are always a string) and the names of the fields in which they change (also a string) and must update the fields of the object with new values
A valid solution with minimum effort would be using a JSON library as a workaround, since they have already implemented value instantiation from strings for the most common types.
For example, using ObjectMapper:
Map<String,String> data = new HashMap<>();
data.put("year","2018");
data.put("name", "BMW");
data.put("type", "TWO");
ObjectMapper mapper = new ObjectMapper();
Car car = mapper.readValue(mapper.writeValueAsString(data), Car.class);
Reflection is indeed the way to go. You can get the type using field.getType() and then check for concrete classes using Class.isAssignableFrom():
final Class<?> type = field.getType();
if (int.class.isAssignableFrom(type)) {
typedValue = Integer.parseInt(value);
} else if (type.isEnum()) {
typedValue = Enum.valueOf((Class<Enum>) type, value);
} else {
// Assume String
typedValue = value;
}
Of course this can become almost arbitrarily complex, but here's a fully working sample for your provided values. That should give you a gist on how to proceed:
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
class CarFiller {
public static void main(String[] args) throws Exception {
Map<String, String> data = new HashMap<>();
data.put("name", "BMW");
data.put("year", "2018");
data.put("type", "TWO");
Car car = new Car();
fillField(car, "name", data);
fillField(car, "year", data);
fillField(car, "type", data);
System.out.println(car);
}
private static void fillField(Object instance, String fieldName, Map<String, String> data) throws Exception {
Field field = instance.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
String value = data.get(fieldName);
Object typedValue = null;
final Class<?> type = field.getType();
if (int.class.isAssignableFrom(type)) {
typedValue = Integer.parseInt(value);
} else if (type.isEnum()) {
typedValue = Enum.valueOf((Class<Enum>) type, value);
} else {
// Assume String
typedValue = value;
}
field.set(instance, typedValue);
}
enum Type {
ONE, TWO
}
static class Car {
private String name;
private int year;
private Type type;
public void setName(String name) {
this.name = name;
}
public void setYear(int year) {
this.year = year;
}
public void setType(Type type) {
this.type = type;
}
#Override
public String toString() {
return "Car [name=" + name + ", year=" + year + ", type=" + type + "]";
}
}
}
(See also on ideone)
I'd recommend not to use reflection everywhere it's possible. Like in your exact example.
You can create an enum class wich contains BiConsumers for each of your fields in Car class:
import java.util.Map;
import java.util.function.BiConsumer;
#Data
public class Car {
private String name;
private int year;
private Ttc.Type type;
static enum CarEnum {
name((car, value) -> car.setName(value)),
year((car, value) -> car.setYear(Integer.parseInt(value))),
type((car, value) -> car.setType(Ttc.Type.valueOf(value)));
private BiConsumer<Car, String> setValueConsumer;
CarEnum(BiConsumer<Car, String> setValueConsumer) {
this.setValueConsumer = setValueConsumer;
}
static Car createCar(Map<String, String> data) {
Car car = new Car();
data.forEach((key, value) -> valueOf(key).setValueConsumer.accept(car, value));
return car;
}
}
}
And then use it in the next way:
Map<String, String> data = new HashMap<>();
data.put("name", "BMW");
data.put("year", "2018");
data.put("type", "TWO");
Car.CarEnum.createCar(data);
Java is statically typed. Therefore you need to provide the correct type yourself. Integer.valueOf takes a String and returns an Integer.
int year = Integer.valueOf("2018");
Converting a String to an Enum works the same.
Type type = Type.valueOf("ONE");
Enum.valueOf is called in the background.
Of course you also need to add some error checking.
I'd recommand avoiding the use of reflection in such a case. You could use a different approach, e.g.
class Car
{
private final Type type;
private final String name;
private final int year;
private Car(Builder builder)
{
this.type = builder.type;
this.name = builder.name;
this.year = builder.year;
}
static class Builder
{
private Type type;
private String name;
private int year;
public Builder setType(String type)
{
this.type = Type.valueOf(type);
return this;
}
public Builder setName(String name)
{
this.name = name;
return this;
}
public Builder setYear(String year)
{
this.year = Integer.valueOf(year);
return this;
}
public Car build()
{
return new Car(this);
}
}
}
You could also add a setData method to the builder
public Builder setData(Map<String, String> data)
{
this.year = Integer.valueOf(data.get("year"));
this.type = Type.valueOf(data.get("type"));
// etc.
return this;
}
Then create a car with Car c = new Car.Builder().setData(data).build();.

Java JSON Processing

I'm having a hard time processing the below JSON with Java, which is being returned from on an external Ansible playbook:
{"Sample":
{
"tag_description":"abc","tag_category_id":"def","tag_id":"ghi"
},
"Sample1":
{
"tag_description":"jkl","tag_category_id":"mno","tag_id":"pqr"
}
}
I've been able to successfully parse one section of the JSON using a custom deserializer, though it only ever gets the first section. Any ideas are hugely appreciated.
#JsonComponent
public class TagSerializer extends JsonDeserializer<Tag> {
#Override
public Tag deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException,
JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
JsonFactory factory = mapper.getFactory();
JsonNode treeNode = jsonParser.getCodec().readTree(jsonParser);
Iterator<Map.Entry<String, JsonNode>> fields = treeNode.fields();
String name = "";
// collect the tag name
Map.Entry<String, JsonNode> entry = fields.next();
name = entry.getKey();
// now that we have the tag name, parse it as a separate JSON object
JsonNode node = entry.getValue();
// get the values from the JSON
String description = node.get("tag_description").asText();
String category_id = node.get("tag_category_id").asText();
String tag_id = node.get("tag_id").asText();
return new Tag(name, category_id, description, tag_id);
}
}
I'm calling the method from a Spring Boot REST API endpoint, and my 'tag' model is a Spring entity:
'Tag' model:
#Entity
#JsonDeserialize(using = TagSerializer.class)
public class Tag {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private String tag_category_id;
private String tag_description;
private String tag_id;
//JPA requires that a default constructor exists
//for entities
protected Tag() {}
public Tag(String name,
String tag_category_id,
String tag_description,
String tag_id) {
this.name = name;
this.tag_category_id = tag_category_id;
this.tag_description = tag_description;
this.tag_id = tag_id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTag_category_id() {
return tag_category_id;
}
public void setTag_category_id(String tag_category_id) {
this.tag_category_id = tag_category_id;
}
public String getTag_description() {
return tag_description;
}
public void setTag_description(String tag_description) {
this.tag_description = tag_description;
}
public String getTag_id() {
return tag_id;
}
public void setTag_id(String tag_id) {
this.tag_id = tag_id;
}
public String toString() {
return "<Tag:[Name: " + this.name + "],[tag_category: "+
this.tag_category_id + "],[tag_description: "+
this.tag_description + "],[tag_id:"+this.tag_id+"]";
}
}
Spring Boot endpoint:
#PostMapping(value="/store", consumes = APPLICATION_JSON_VALUE)
public void tagJson(#RequestBody String json) {
// delete any existing tags
tagRepository.deleteAll();
//lets modify the json to make it look nicer
String modjson = "["+json+"]";
ObjectMapper mapper = new ObjectMapper();
try {
Tag[] tags = mapper.readValue(modjson, Tag[].class);
for (Tag t : tags)
tagRepository.save(t);
} catch (Exception exception) {
exception.printStackTrace();
}
}
If you are using Spring MVC consider explicitly declare desired type when referreing to #RequestBody and let the framework do the job for you
#PostMapping(value="/store", consumes = APPLICATION_JSON_VALUE)
public void tagJson(#RequestBody Map<String, Tag> json) {
// Do not mess with ObjectMapper here, Spring will do the thing for you
}
This isn't a direct answer but a guide in a possible direction, using Gson.
package test;
import java.util.Map;
import com.google.gson.Gson;
public class JsonTest {
public static void main(final String... args) {
new JsonTest().run();
}
public void run() {
final Gson gson = new Gson();
final Map<?, ?> result = gson.fromJson("{" +
" \"Sample\": {" +
" \"tag_description\": \"abc\"," +
" \"tag_category_id\": \"def\"," +
" \"tag_id\": \"ghi\"" +
" }," +
" \"Sample1\": {" +
" \"tag_description\": \"jkl\"," +
" \"tag_category_id\": \"mno\"," +
" \"tag_id\": \"pqr\"" +
" }" +
"}", Map.class);
System.out.println("Map size: " + result.size());
}
}
The resulting size is 2. The map entries are keyed Sample, Sample1, and the values are lists containing the nodes. You can see this using a debugger.

Jackson - parse different model under same key at runtime

I have a specific json response from server, where under a key the content would be of different models also at a time only one of the model data would be present under the key.
While parsing the response into POJO how can I specify object type at runtime based on other field of contentType on same model.
Following is the code for better understanding of scenario.
Here content_type is type A and so under "content" key there would be model for object of class TypeA
"scheduled_content": {
"some_field": "value",
"content_type": "typeA",
"content" : {
"some_field" : "value"
"more_feilds" : "value"
}
}
Here content_type is type B and so under "content" key there would be model for object of class TypeB
"scheduled_content": {
"some_field": "value",
"content_type": "typeB",
"content" : {
"some_field_b" : "value"
"more_fields_for_b" : "value"
}
}
How can I write POJO classes to parse such json response?
The type classes are completely different models they don't have any field in common.
I believe that what you are looking for is called, in Jackson JSON terms, polymorphic deserialization by property name.
Here is how I do it with Jackson 2.1.4:
First create an abstract class ScheduledContent with common members and an abstract method that would operate on the content. Use the JsonTypeInfo annotation to mark the JSON property that would resolve the specific implementation and the JsonSubTypes annotation to register the subtypes by the values of the property previously specified:
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "content_type")
#JsonSubTypes({
#JsonSubTypes.Type(name = "typeA", value = ScheduledAContent.class),
#JsonSubTypes.Type(name = "typeB", value = ScheduledBContent.class)
})
public abstract class ScheduledContent {
private String someField;
#JsonSetter("some_field")
public void setSomeField(String someField) {
this.someField = someField;
}
public abstract void doSomethingWithContent();
}
The subtypes registration can also be done on the ObjectMapper as you will see later.
Then add the specific implementation for the ScheduledAContent class:
public class ScheduledAContent extends ScheduledContent {
private TypeAContent content;
public void setContent(TypeAContent content) {
this.content = content;
}
#Override
public void doSomethingWithContent() {
System.out.println("someField: " + content.getSomeField());
System.out.println("anotherField: " + content.getAnotherField());
}
}
with TypeAContent being:
import com.fasterxml.jackson.annotation.JsonSetter;
public class TypeAContent {
private String someField;
private String anotherField;
#JsonSetter("some_field")
public void setSomeField(String someField) {
this.someField = someField;
}
public String getSomeField() {
return someField;
}
#JsonSetter("another_field")
public void setAnotherField(String anotherField) {
this.anotherField = anotherField;
}
public String getAnotherField() {
return anotherField;
}
}
and also for the ScheduledBContent class:
public class ScheduledBContent extends ScheduledContent {
private TypeBContent content;
public void setContent(TypeBContent content) {
this.content = content;
}
#Override
public void doSomethingWithContent() {
System.out.println("someField: " + content.getSomeField());
System.out.println("anotherField: " + content.getAnotherField());
}
}
with TypeBContent being:
import com.fasterxml.jackson.annotation.JsonSetter;
public class TypeBContent {
private String someField;
private String anotherField;
#JsonSetter("some_field_b")
public void setSomeField(String someField) {
this.someField = someField;
}
public String getSomeField() {
return someField;
}
#JsonSetter("another_field_b")
public void setAnotherField(String anotherField) {
this.anotherField = anotherField;
}
public String getAnotherField() {
return anotherField;
}
}
And a simple Test class:
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;
public class Test {
public static void main(String[] args) {
String jsonA = "{" +
"\"some_field\": \"main_some_field1\"," +
"\"content_type\": \"typeA\"," +
"\"content\" : {" +
" \"some_field\" : \"content_some_field\"," +
" \"another_field\" : \"content_another_field\"" +
"}}";
String jsonB = "{" +
"\"some_field\": \"main_some_field2\"," +
"\"content_type\": \"typeB\"," +
"\"content\" : {" +
" \"some_field_b\" : \"content_some_field_b\"," +
" \"another_field_b\" : \"content_another_field_b\"" +
"}}";
ObjectMapper mapper = new ObjectMapper();
/*
* This is another way to register the subTypes if you want to do it dynamically without the use of the
* JsonSubTypes annotation in the ScheduledContent class
*/
// mapper.registerSubtypes(new NamedType(ScheduledAContent.class, "typeA"));
// mapper.registerSubtypes(new NamedType(ScheduledBContent.class, "typeB"));
try {
ScheduledContent scheduledAContent = mapper.readValue(jsonA, ScheduledContent.class);
scheduledAContent.doSomethingWithContent();
ScheduledContent scheduledBContent = mapper.readValue(jsonB, ScheduledContent.class);
scheduledBContent.doSomethingWithContent();
} catch (IOException e) {
e.printStackTrace();
}
}
}
that will produce the output:
someField: content_some_field
anotherField: content_another_field
someField: content_some_field_b
anotherField: content_another_field_b
Using #JsonSetter in the setter methods may help. But in this case you will need to create setter methods for each type of fields in "content".
#JsonSetter("some_field")
public void setSomeField1(String field1) {
this.field1 = field1;
}
#JsonSetter("some_field_b")
public void setSomeField2(String field2) {
this.field1 = field1;
}

Jackson Serialization for subclasses

In the below example, I have a primary class - A and its subclass - B. Both can be used as a property in the general class X.
public class A
{
#JsonProperty("primary_key")
public final String primaryKey;
#JsonCreator
A(#JsonProperty("primary_key") String primaryKey)
{
this.primaryKey = primaryKey;
}
}
public class B extends A
{
#JsonProperty("secondary_key")
public final String secondaryKey;
#JsonCreator
B(#JsonProperty("primary_key") String primaryKey, #JsonProperty("secondary_key") String secondaryKey)
{
super(primaryKey);
this.secondaryKey = secondaryKey;
}
}
public class X
{
#JsonProperty("keys")
public final A keys;
#JsonCreator
X(#JsonProperty("keys") A keys)
{
this.keys = keys;
}
}
How can I use Jackson Polymorphic feature in order to correctly deserialize the below given json into their respective classes:
JSON A :
{ "keys" :{
"primary_key" : "abc"
}
}
JSON B :
{ "keys" : {
"primary_key" : "abc",
"secondary_key" : "xyz"
}
}
Expected Result: Map keys object to Class A for JSON A and Class B for JSON B.
Please suggest alternative suggestions too.
It feels like a pretty common problem and there is no easy annotations way to solve it (Or maybe i just cant find one):
Jackson Polymorphic Deserialization - Can you require the existence of a field instead of a specific value?
Deserializing polymorphic types with Jackson
One thing you can do is to add custom deserializer to your object mapper. Here is nice demo of this approach: https://stackoverflow.com/a/19464580/1032167
Here is demo related to your example:
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
public class Main4 {
private static final String jsonA = "{ \"keys\" : { \"primary_key\" : \"abc\" } }";
private static final String jsonB =
"{ \"keys\" : { \"primary_key\" : \"abc\", \"secondary_key\" : \"xyz\" } }";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule idAsRefModule = new SimpleModule("ID-to-ref");
idAsRefModule.addDeserializer(A.class, new AJsonDeserializer());
mapper.registerModule(idAsRefModule);
X tl = mapper.readValue(jsonA, X.class);
System.out.println(tl);
X t2 = mapper.readValue(jsonB, X.class);
System.out.println(t2);
}
public static class AJsonDeserializer extends JsonDeserializer<A>{
#Override
public A deserialize(JsonParser jp, DeserializationContext dc)
throws IOException {
ObjectCodec codec = jp.getCodec();
JsonNode node = codec.readTree(jp);
if (node.has("secondary_key")) {
return codec.treeToValue(node, B.class);
}
return new A(node.findValue("primary_key").asText());
}
}
public static class A
{
#JsonProperty("primary_key")
public final String primaryKey;
#JsonCreator
A(#JsonProperty("primary_key") String primaryKey)
{
this.primaryKey = primaryKey;
}
#Override
public String toString() {
return "A{" +
"primaryKey='" + primaryKey + '\'' +
'}';
}
}
public static class B extends A
{
#JsonProperty("secondary_key")
public final String secondaryKey;
#JsonCreator
B(#JsonProperty("primary_key") String primaryKey,
#JsonProperty("secondary_key") String secondaryKey)
{
super(primaryKey);
this.secondaryKey = secondaryKey;
}
#Override
public String toString() {
return "B{" +
"primaryKey='" + primaryKey + '\'' +
"secondaryKey='" + secondaryKey + '\'' +
'}';
}
}
public static class X
{
#JsonProperty("keys")
public final A keys;
#JsonCreator
X(#JsonProperty("keys") A keys)
{
this.keys = keys;
}
#Override
public String toString() {
return "X{" +
"keys=" + keys +
'}';
}
}
}
But you will have to create one more super class if you want to use default A deserializer or look here how you can solve this: https://stackoverflow.com/a/18405958/1032167
If I understoon correctly, simply passing the values will work, without any config. I believe this is what you are looking for:
public class Test {
private static final String JSON = "{\"keys\":{\"primary_key\":\"abc\",\"secondary_key\":\"xyz\"}}";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
X x = mapper.readValue(JSON, X.class);
System.out.println(mapper.writeValueAsString(x));
}
}
class A {
private String primary_key;
public String getPrimary_key() {
return primary_key;
}
public void setPrimary_key(String primary_key) {
this.primary_key = primary_key;
}
}
class B extends A {
private String secondary_key;
public String getSecondary_key() {
return secondary_key;
}
public void setSecondary_key(String secondary_key) {
this.secondary_key = secondary_key;
}
}
class X {
private B keys;
public B getKeys() {
return keys;
}
public void setKeys(B keys) {
this.keys = keys;
}
}
Output will be:
{"keys":{"primary_key":"abc","secondary_key":"xyz"}}
In case this is not what you expect, please provide another explanation and I will edit the answer as needed.

Categories