Jackson deserialize dynamically by class fully Qualified Name - java

I am working on the framework which has these requirements
Developers develop their classes ( Beans ) like below
class BeanA{
int id;
String appName;
public void save()
}
class BeanB{
int id;
String userName;
public void save()
}
There is orchestratoon.csv is also defined like this where developer makes the entry of their class
org.example.BeanA, http://fetch/bean/a/from/this/url
org.example.BeanB, http://fetch/bean/b/from/this/url
org.example.BeanC, http://fetch/bean/c/from/this/url
After this, I have to write a platform service, which will fetch the Beans data from the url given in the orchestration.csv file and render then in the class provided by their fully qualified name
My problem is, how can I deserialize the json fetched into the class given by its name and call a save method of that bean.
Below is the psuedo code.
ObjectMapper objectMapper = new ObjectMapper();
for className, URL in CSV
String json = getObject(url)
// Here is the problem, how do I deserialize it using ObjectMapper
objectMapper.readValue(json, Class.forName(className)); ---> It does not work , compile time error because of Class.forName and how do I call a save on it
Any help would

Extract the save() method in an interface or abstract class, depending on your exact scenario. In most cases interface is better choice:
public interface MyInterface {
void save();
}
Have BeanA, BeanB, etc. implement it. Cast the deserialization result to MyInterface and invoke save().
Object object;
try {
object = objectMapper.readValue(json, Class.forName("bean.class.full.name"));
} catch (JsonProcessingException exc) {
throw new RuntimeException("invalid json", exc);
}
if (object instanceof MyInterface myInterface) {
myInterface.save();
} else {
throw new RuntimeException("appropriate message here");
}

Assuming that you managed to read the JSON string that represents particular class from your URL and you know the class name. So you can use ObjectMapper class and its method <T> T readValue(String content, Class<T> valueType). Place your json string as a content and as for class use public static Class<?> forName(String className) throws ClassNotFoundException. See ObjectMapper javadoc here.
Also, if you want it even simpler I wrote my own JsonUtil where you don't even have to instantiate ObjectMapper. Your code would look like this:
try {
BeanB beanB = JsonUtils.readObjectFromJsonString(jsonStr, Class.forName(classNameStr));
} catch (IOException ioe) {
...
}
In this example class JsonUtils comes with Open Source MgntUtils library written and maintained by me. See the Javadoc for JsonUtils class. The MgntUtils library can be obtained from Maven Central as Maven artifact or from Github along with Source code and Javadoc

Related

Parsing Json using FasterXML for generic elements

I´m using java spring boot to read data via REST api.
Incoming data is specified as followed:
public class Element<T>
{
public String error;
public Boolean state;
public T subelement;
}
public class Response<T>
{
public Element<T> element;
}
Generic Type T can be any generic class such as Boolean or complex classes.
In a simple sample I tried to parse an Response element:
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(value, Response<Boolean>.class);
Eclipse marks me the Response.class part as followed :
Boolean cannot be resolved to a variable
I´m used to use generics on fly in order to dynamicly interact in Delphi and .NET. There has never been the urge of generating classes or pass types since you can classify T such being a class, owning an empty constructor and json wrapper takes T arguments.
Since JacksonXML requires a Class I need to find a way to solve it.
I tried a few posts here on stack overflow to obtain data on following way:
ParameterizedType
JacksonXMLbind Javatype
any ideas?
Edit:
Well it could be based on the List Element that it looks differently.
Here is a sample of my json:
{"id":1,"state":true,"element":{"response":{"data":{"client_id":"sock1591427021","protocol_version":2}}},"errormsg":"OK"}
this part i use to convert the json :
private <T extends Object> APIResponse<T> readResponse(String data) {
try {
var value = objectMapper.readValue(data, new TypeReference<APIResponse<T>>() {
});
return value;
} catch (Exception e) {
logger.warn(e.getMessage());
return null;
}
}

How to use auto-value with firebase 9.2 in Android

I want to use auto-value with firebase 9.2.0+. I have the following code:
#AutoValue
public abstract class Office {
public static Builder builder() {
return new AutoValue_Office.Builder();
}
public abstract double latitud();
public abstract double longitud();
#AutoValue.Builder
public static abstract class Builder {
public abstract Builder latitud(double latitud);
public abstract Builder longitud(double longitud);
public abstract Office build();
}
}
But when I make to call this Office office = childDataSnapshot.getValue(Office.class); I am getting this error:
com.google.firebase.database.DatabaseException: No properties to serialize found on class com.example.app.model.Office
Somebody have an idea why I am getting this error and how to solve it? I read that firebase is no longer using jackson for json serialization. So I am not sure how to specify a kind of #JsonProperty("latitud") I have used #PropertyName unsuccessfully.
I also tried rename the abstract methods like public abstract double getLatitud(); and after that the error is the next one:
java.lang.InstantiationException: Can't instantiate abstract class com.example.app.model.Office
So I am not sure how to solve this.
SOLUTION
Thanks to hatboysam and Frank van Puffelen I finally could face this problem with the next solution.
I created a FirebaseUtil enum with two methods for serialize and deserialize objects for Firebase based on hatboysam answer and Frank van Puffelen comment.
I create a couple of User and Phone classes for testing.
Dependencies:
compile 'com.fasterxml.jackson.core:jackson-annotations:2.8.0'
compile 'com.fasterxml.jackson.core:jackson-databind:2.8.0'
Usage example:
User user = FirebaseUtil.deserialize(dataSnapshot, User.class);
Map<String, Object> map = FirebaseUtil.serialize(user);
I'm not sure this is possible with the default Firebase data mapper, but there is a possible workaround. First let's explain the errors you're seeing:
com.google.firebase.database.DatabaseException: No properties to serialize found on class com.example.app.model.Office
The Firebase mapper looks for either public fields or fields named with the getFoo/setFoo pattern. So on your class the mapper does not see any properties.
java.lang.InstantiationException: Can't instantiate abstract class com.example.app.model.Office
This is the one I think you'll have trouble getting around. In order for the deserialization to work your class needs to have a public, no-argument constructor that the mapper can call via reflection (newInstance()). As far as I know this is not how AutoValue works.
But don't lose hope!. According to this github issue there is a way to make Jackson and AutoValue compatible using the #JsonCreator annotation. So you'll need to use both Jackson and Firebase to get the job done here.
Serializing:
// Convert to a Map<String,Object> using Jackson and then pass that to Firebase
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.convertValue(office, Map.class);
databaseReference.setValue(map);
Deserializing:
// Use Firebase to convert to a Map<String,Object>
GenericTypeIndicator<Map<String,Object>> t = new GenericTypeIndicator<Map<String,Object>>() {};
Map<String,Object> map = dataSnap.getValue(t);
// Use Jackson to convert from a Map to an Office object
ObjectMapper mapper = new ObjectMapper();
Office pojo = mapper.convertValue(map, Office.class);
I wrote an AutoValue extension for this:
https://github.com/mattlogan/auto-value-firebase
The extension generates a firebase-compatible class, called FirebaseValue, as a static inner class in your generated AutoValue class. You can convert between your AutoValue class and your FirebaseValue class via the generated constructors.
Here's an example, copied from the readme, of what that looks like:
#AutoValue #FirebaseValue
public abstract class Taco {
public static Taco create(String name, List<Ingredient> ingredients, Review review) {
return new AutoValue_Taco(name, ingredients, review);
}
// Create AutoValue_Taco instance from AutoValue_Taco.FirebaseValue instance
public static Taco create(DataSnapshot dataSnapshot) {
AutoValue_Taco.FirebaseValue taco = dataSnapshot.getValue(AutoValue_Taco.FirebaseValue.class);
return new AutoValue_Taco(taco);
}
// Create AutoValue_Taco.FirebaseValue instance from AutoValue_Taco instance
public Object toFirebaseValue() {
return new AutoValue_Taco.FirebaseValue(this);
}
public abstract String name();
public abstract List<Ingredient> ingredients();
public abstract Review review();
}

java jackson parse object containing a generic type object

i have the following problem.
I have to parse a json request into an object that contains a generic type field.
EDIT
i have made some tests using a regular class type (so i make it work before i replace it with generic). Now parsing for a single element works great.
The issue is when i need to parse out a list object out of that class.
So i have to inform jackson somehow that my T is of type list instead of just AlbumModel.
Here is what i have tried.
#Override
public ListResponseModel<AlbumModel> parse(String responseBody) throws Exception {
JavaType type = mapper.getTypeFactory().constructParametricType(ResponseModel.class,
AlbumModel.class);
return mapper.readValue(responseBody,
mapper.getTypeFactory().constructParametricType(ResponseModel.class, type));
}
But the code above doesn't work. what is the solution for something like this?
my generic type in the ListResponseModel is defined like: List<T> data
succeeded like:
public class BaseResponseModel<T> {
#JsonProperty("data")
private T data;
#JsonProperty("paginations")
private PaginationModel pagination;
}
so far i have the following code but it always parses into a Hash.
public class ResponseParser extends BaseJacksonMapperResponseParser<ResponseModel<AlbumModel>> {
public static final String TAG = ResponseParser.class.getSimpleName();
#Override
public ResponseModel<AlbumModel> parse(String responseBody) throws Exception {
return mapper.readValue(responseBody,
mapper.getTypeFactory().constructParametricType(ResponseModel.class, AlbumModel.class));
}
}
public abstract class BaseJacksonMapperResponseParser<T> implements HttpResponseParser<T> {
public static final String TAG = BaseJacksonMapperResponseParser.class.getSimpleName();
public static ObjectMapper mapper = new ObjectMapper();
static {
mapper.disable(Feature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
}
}
I agree with eugen's answer but just wanted to expand on it a bit. The first step is to refactor your parse method so it takes a second argument. Instead of allocating the type reference in your method, you require the caller to pass in a TypeReference instance.
public BaseResponseModel<T> parse(String responseBody, TypeReference<T> ref) throws Exception {
return mapper.readValue(responseBody, ref);
}
Unfortunately your snippet does not show the code which calls parse - so I'll make something up:
BaseResponseParser<Collection<Person>> parser = new BaseResponseParser<Collection<Person>>();
BaseResponseModel<Collection<Person>> result = parser.parse(jsonText, new TypeReference<Collection<Person>>(){});
Notice that when the TypeReference instance is compiled in this case, it a type reference to the real concrete class that we expect.
You could do the same thing passing in a Class at runtime, however TypeReference is a bit more powerful because it even works when type T is a generic collection. There is some magic in the TypeReference implementation that allows it to hold onto type information that would normally be erased.
[update]
Updated to use Collection<Person>. Note - as far as I know as List<Whatever> should work also, but I double checked a project where I was using jackson to deserialize collections. Base class Collection definitely worked so I stayed with that.
Your type T will be "erased" at runtime, so Jackson does not know what is the real type of T and deserializes it to a Map. You need a second parameter to your parse method that will be Class<T> clazz or TypeReference<T> or java.lang.reflect.Type.
EDIT
Small explanation on the magic of TypeReference. When you do new XX() {} you are creating a anonymous class, so if it is a class with typevariables (parameterized if you prefer), new X<List<Y>>() {}, you will be able to retrieve List<Y> as a java Type at runtime. It is very similar as if you had done :
abstract class MyGenericClass<T> {}
class MySpecializedClass extends MyGenericClass<List<Y>> {}
Since you're using Jackson you probably need to create a custom JsonDeserializer or JsonSerializer depending on whether you're handing the response or request. I've done this with Dates because on my response I want a standard view. I'm not 100% positive it will work with a generic field though. Here is an example of what I'm doing:
public class DateSerializer extends JsonSerializer<Date> {
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZ");
#Override
public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
String dateString = dateFormat.format(value);
jgen.writeString(dateString);
}
}
Then I just add it to my class like so:
#JsonSerialize(using = DateSerializer.class)
public Date getModifiedDate() {
return modifiedDate;
}

Gson add field during serialization

I can't find a simple way to add a custom field during serialization in Gson and I was hoping someone else may be able to help.
Here is a sample class to show my issue:
public class A {
String id;
String name;
...
}
When I serialize class A I would like to return something like:
{ "id":"123", "name":"John Doe", "url_to_user":"http://www.example.com/123" }
where url_to_user is not stored in my instance of class A, but can be generated with data in the instance of class A.
Is there a simple way of doing this? I would prefer to avoid writing an entire serializer just to add one field.
Use Gson.toJsonTree to get a JsonElement, with which you can interact dynamically.
A a = getYourAInstanceHere();
Gson gson = new Gson();
JsonElement jsonElement = gson.toJsonTree(a);
jsonElement.getAsJsonObject().addProperty("url_to_user", url);
return gson.toJson(jsonElement);
Well, the top rated answer is quite a quick one and not essentially bad when you are lacking much time but here is the problem: There is no proper separation of concern
You are modifying the serialized JSON at the same place where you are writing your business logic. You should be doing all the serialization inside of a TypeAdapter or a JsonSerializer.
How can we maintain a proper separation of concern?
The answer wraps around a bit of additional complexity but the architecture demands it. Here we go(taken from my other answer):
First, we would be using a custom serializer for the type. Second, we would have to create a copy constructor inside the base class and a wrapper subclass as follows:
Note: The custom serializer might seem like an overkill but trust me, it pays off in long run for maintainability.
.
// Lets say the base class is named Cat
public class Cat {
public String name;
public Cat(String name) {
super();
this.name = name;
}
// COPY CONSTRUCTOR
public Cat(Cat cat) {
this.name = cat.name;
}
#Override
public String sound() {
return name + " : \"meaow\"";
};
}
// The wrapper subclass for serialization
public class CatWrapper extends Cat{
public CatWrapper(String name) {
super(name);
}
public CatWrapper(Cat cat) {
super(cat);
}
}
And the serializer for the type Cat:
public class CatSerializer implements JsonSerializer<Cat> {
#Override
public JsonElement serialize(Cat src, Type typeOfSrc, JsonSerializationContext context) {
// Essentially the same as the type Cat
JsonElement catWrapped = context.serialize(new CatWrapper(src));
// Here, we can customize the generated JSON from the wrapper as we want.
// We can add a field, remove a field, etc.
// The main logic from the top rated answer now here instead of *spilling* around(Kindly ignore the cat having a url for the sake of example)
return catWrapped.getAsJsonObject().addProperty("url_to_user", url);
}
}
So, why a copy constructor?
Well, once you define the copy constructor, no matter how much the base class changes, your wrapper will continue with the same role. Secondly, if we don't define a copy constructor and simply subclass the base class then we would have to "talk" in terms of the extended class, i.e, CatWrapper. It is quite possible that your components talk in terms of the base class and not the wrapper type.

Serialize one class in two different ways with Jackson

In one of our projects we use a java webapp talking to a MongoDB instance. In the database, we use DBRefs to keep track of some object relations. We (de)serialize with POJO objects using jackson (using mongodb-jackson-mapper).
However, we use the same POJOs to then (de)serialize to the outside world, where our front end deals with presenting the JSON.
Now, we need a way for the serialization for the outside world to contain the referenced object from a DBRef (so that the UI can present the full object), while we obviously want to have the DBRef written to the database, and not the whole object.
Right now I wrote some untested static nested class code:
public static class FooReference {
public DBRef<Foo> foo;
// FIXME how to ensure that this doesn't go into the database?
public Foo getFoo() {
return foo.fetch();
}
}
Ideally I would like a way to annotate this so that I could (de)serialize it either with or without the getFoo() result, probably depending on some configuration object. Is this possible? Do you see a better way of going about doing this?
From looking at options, it seems you can annotate properties to only be shown if a given View is passed to the ObjectMapper used for serialization. You could thus edit the class:
public static class FooReference {
public DBRef<Foo> foo;
#JsonView(Views.WebView.class)
public Foo getFoo() {
return foo.fetch();
}
}
and provide:
class Views {
static class WebView { }
}
and then serialize after creating a configuration with the correct view:
SerializationConfig conf = objectMapper.getSerializationConfig().withView(Views.WebView.class);
objectMapper.setSerializationConfig(conf);
Which would then serialize it. Not specifying the view when serializing with the MongoDB wrapper would mean the method would be ignored. Properties without a JsonView annotation are serialized by default, a behaviour you can change by specifying:
objectMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
More info is available on the Jackson Wiki.
There are still other alternatives, too, it turns out: there are Jackson MixIns which would let you override (de)serialization behaviour of parts of a class without modifying the class itself, and as of Jackson 2.0 (very recent release) there are filters, too.
Use a custom JSONSerializer and apply your logic in the serialize method:
public static class FooReference {
public DBRef<Foo> foo;
#JsonSerialize(using = CustomSerializer.class)
public Foo getFoo() {
return foo.fetch();
}
}
public class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
// jgen.writeObjectField ...
}
}

Categories