Issue with replacing Java DTO classes with Kotlin Data classes - java

I read about Kotlin data classes and thought that they could be pretty useful in cases with describing data transfer objects (DTOs). In my Java project I already has DTO classes written on Java, something like:
public class Tweet {
private String id;
private String profileId;
private String message;
public Tweet() {}
public String getId() {
return id;
}
public String getProfileId() {
return profileId;
}
public String getMessage() {
return message;
}
public void setId(String id) {
this.id = id;
}
public void setProfileId(String profileId) {
this.profileId = profileId;
}
public Tweet setMessage(String message) {
this.message = message;
return this;
}
}
These DTO classes are stored in separate artifact which I add as a dependency to other artifacts. So, I decided to replace it with Kotlin classes and rewrote mentioned Tweet class on Kotlin, so it started to looks like:
data class Tweet(var id: String? = null,
var profileId: String? = null,
var message: String? = null)
It's my first experience with Kotlin, so possibly there are something that can looks ugly, but my main issue is - when I try to rebuild artifacts which use my DTOs as dependencies, I get such exception:
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2992)
at io.vertx.core.json.Json.decodeValue(Json.java:117)
at gk.tweetsched.api.repository.TweetRepository.get(TweetRepository.java:51)
at gk.tweetsched.api.repository.TweetRepositoryTest.testGet(TweetRepositoryTest.java:68)
Caused by: java.lang.ClassNotFoundException:
kotlin.jvm.internal.DefaultConstructorMarker
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 67 more
As I see according that stacktrace Jackson couldn't deserialize JSON to Tweet Kotlin class.
Here is my Java method where I get that exception:
public Tweet get(String id) {
try (Jedis jedis = pool.getResource()) {
return Json.decodeValue(jedis.hget(TWEETS_HASH, id), Tweet.class);
} catch (Exception e) {
LOGGER.error(e);
}
return null;
}
Where Json class is from 'io.vertx.core.json' package.
How can I fix that issue? Which additional configurations should I make in my Java projects to use Kotlin classes?

By default Jackson needs a parameterless constructor to deserialize JSON to a class - Kotlin data classes do not have one, so you need to add a Jackson module to handle this:
jackson-module-kotlin
Edit:
I've read the source for io.vertx.core.json.Json class and it seems that both object mappers used by the class are stored in public static fields.
So to register jackson-module-kotlin you need to include this snippet in your application initialization code (or anywhere else really as long as it is executed before you attempt to deserialize any Kotlin data classes):
Json.mapper.registerModule(new KotlinModule())
Json.prettyMapper.registerModule(new KotlinModule())

In my case, I create kotlin class DTO instance in java for consuming RESTful Api.
Now I have 2 solutions tested:
Use parameterless constructor in data class.
The reference kotlin says:
On the JVM, if all of the parameters of the primary constructor have
default values, the compiler will generate an additional parameterless
constructor which will use the default values. This makes it easier to
use Kotlin with libraries such as Jackson or JPA that create class
instances through parameterless constructors.
So I have a DTO in kotlin like this:
data class Dto (
var id: Int?=null,
var version: Int?=null,
var title: String?=null,
var firstname: String?=null,
var lastname: String?=null,
var birthdate: String?=null
)
Then, I create class instance DTO in java:
Dto dto = new Dto();
dto.setId(javadto.getId());
...
Use plugin jackson-module-kotlin
import com.fasterxml.jackson.annotation.JsonProperty
data class Dto (
#JsonProperty("id") var id: Int?,
#JsonProperty("version") var version: Int?,
#JsonProperty("title") var title: String?,
#JsonProperty("firstname") var firstname: String?,
#JsonProperty("lastname") var lastname: String?,
#JsonProperty("birthdate") var birthdate: String?,
)
Then, I create class instance DTO in java:
Dto dto = new Dto(null, null, null, null, null, null);
dto.setId(javadto.getId());
...

Related

Handle Json non existent keys spring boot

I am creating a requestModel and let say a person doesn't send me some keys.
If that key is not present I want to put null if i get the value of the key.
I don't want to investigate if a key is present or not .
public class CustomerModel {
private Optional<String> s3Bucket;
private Optional<String> docType;
public String getS3Bucket() {
if(s3Bucket.isPresent()) {
return s3Bucket.get();
} else {
return null;
}
}
public void setS3Bucket(Optional<String> s3Bucket) {
this.s3Bucket = s3Bucket;
}
public Optional<String> getDocType() {
return docType;
}
public void setDocType(Optional<String> docType) {
this.docType = docType;
}
}
Do we have any library or something where.
1. If i get the key and it is not present in the coming request json, i will get the null out of it and if the key is present and has value . It will be stored as value.
2. When writing the getter for s3bucket (getS3Bucket), i dont want to write it for everykey value. Is there a automatic way to do this.
I looked at lot of posts but the scenario is not there.
P.S - I am new to java
I believe Jackson is exactly what you need. And if you are using Spring - it already uses Jackson under the hood I guess.
Here you can find some examples and documentation of how JSON mapping on to model class is done.
If you need to customize some behavior, you can use annotations like #JsonProperty (there are many).
If properties in your model class have the same names as properties in JSON, most probably you won't need to provide any further configs.
Here is a simple example:
public class User {
#JsonProperty("userName")
private String name;
private int age;
// getters and setters
}
And if you have JSON like this:
{
"userName" : "Foo Bar",
"age" : 18
}
Jackson will do all the magic for you unless you need something very specific.
If something is not in JSON you get (let's say you received JSON without age) - corresponding property in model class will be null if it is object type and default value (0, false, etc.) for primitives (in our case age would be 0).

Kotlin: Change the json property name depending on the #JsonView

I am trying to use Jackson to serialize the same DTO object in 2 different ways, depending on the #JsonView.
I want to use 2 different names for the same field. In one case I want to name the json property myField (just like the class field name), in the other I want it to be named myInternalApiField.
As a result I would like to see outcomes similar to the presented below:
Usage 1 (External API View):
{
"myField": "value1",
"myOtherField": "otherValue"
}
Usage 2 (Internal API View):
{
"myInternalApiField": "value1",
"myOtherField": "otherValue"
}
In my implementation in Java to achieve that I used the combination of custom getters, setters and #JsonView annotation as below:
public class CustomDTO {
#JsonView(Views.ExternalApiView)
private String myField;
// Other fields here
#JsonView(Views.InternalApiView)
public String getMyInternalApiField() { return myField; }
#JsonView(Views.InternalApiView)
public void setMyInternalApiField(String value) { this.myField = value; }
#JsonView(Views.ExternalApiView)
public String getMyField() { return myField; }
#JsonView(Views.ExternalApiView)
public void setMyField(String value) { this.myField = value }
}
However I don't know how to properly achieve the same result in Kotlin.
I was thinking about using something like:
data class CustomDTO(
#get:[JsonView(Views.ExternalApiView) JsonProperty("myField")]
#get:[JsonView(Views.InternalApiView) JsonProperty("myInternalApiField")]
#set:[JsonView(Views.InternalApiView) JsonProperty("myField")]
#set:[JsonView(Views.InternalApiView) JsonProperty("myInternalApiField")]
var myField: String,
val myOtherField: String,
val myDifferentField: String
)
But this is not allowed in Kotlin.
Do you have any suggestions how to utilize the #JsonView in Kotlin in the similar way as I did it in Java?
How about something like:
data class CustomDTO(
#JsonView(ExternalApiView::class)
var myField: String,
val myOtherField: String,
val myDifferentField: String
) {
val myExternalField: String
#JsonView(InternalApiView::class)
get() {
return myField
}
}
It looks like there are ways that don't require creating computed properties in the DTO, like:
Using Jackson Mixins
Creating a custom serializer for a particular invocation
Combining a custom serializer with custom annotations
But these have their own complexity, even if that complexity isn't in the DTO class. I'm not sure these are much more appealing to me but you could see if they appeal to you.

Create POJO Class for Kotlin

I want to create POJO class for Kotlin, as we know that www.jsonschema2pojo.org converts JSON to POJO so we can use it with gson.
Anyone know how to create Gson POJO for Kotlin QUICKLY?
Edited:
I know its use Data classes, but is there any simplest way to create it?
I think this should be the Plugin what you want
https://github.com/wuseal/JsonToKotlinClass
Yes, I got solution
for Example:
{
"foo": "string",
"bar": "integer",
"baz": "boolean"
}
My POJO Class Created using http://www.jsonschema2pojo.org/
Example.java
public class Example {
#SerializedName("foo")
#Expose
private String foo;
#SerializedName("bar")
#Expose
private String bar;
#SerializedName("baz")
#Expose
private String baz;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
public String getBaz() {
return baz;
}
public void setBaz(String baz) {
this.baz = baz;
}
}
Converted Kotlin Class using Code -> Convert Java File to Kotlin File or CTRL + ALT + SHIFT + K
Example.kt
class Example {
#SerializedName("foo")
#Expose
var foo: String? = null
#SerializedName("bar")
#Expose
var bar: String? = null
#SerializedName("baz")
#Expose
var baz: String? = null
}
Thank you all.
data class ModelUser(val imagePath: String,val userName: String)
Unbelievable Right! Its as simple as that. Just use data keyword before class to create Data class in Kotlin.
Data class provides you with everything, getters, setters, hashCode, toString and equals functions. So all you have to do is create an instance and start using the functions.
A feature request about Kotlin support to auto generate data classes have been filled here in jsonschema2pojo github repository.
Currently, there is no jsonschema2kotlin web utility available.
If you don't have any problem installing a new plugin on Android Studio, follow the accepted answer, otherwise the best you can do is to use jsonschema2pojo to convert JSON to Java POJO and the use the Android Studio 3.0+ feature that converts a Java file to a Kotlin one.
In vs-code there is a plugin named Paste JSON as Code.
it supports many languages.
Paste Json as code
If I got your question, you might be searching some plugin for converting to POJO. So
RoboPOJOGenerator
may help you. You can use a plugin from File>Setting>Plugin>Browse Repositories and search for RoboPOJOGenerator.
To use this plugin you first need to create a separate package like "data", right-click the package and you will see Generate POJO from JSON. Also, you need to include gson library in gradle because this plugin will automatically generate annotation of gson like #SerializedName, etc.
Try this
This is the simple way
Right-click on the package name and select New->Kotlin File/Class
Name the name (in my case, I am naming this as Model, you can name it whatever you like) and click OK.
Paste this code, this is your POJO/Model class:
class Model {
var uid: String? = null
var name: String? = null
}
How to use this
val model = Model()
model.name = "Sunil"
Log.e("Model after", model.name)
Use the Android Studio or IntelliJ IDEA plugin:
JSON To Kotlin Class ​(JsonToKotlinClass)​
In my case Code -> Generate doesn't work, it is disabled (see screenshot)
You should install the plugin "JsonToKotlinClass"
Then right-click on the namespace and select
Paste your JSON here. That's all, profit.
data class VideoGame(val name: String, val publisher: String, var reviewScore: Int)
//Constructor
val game: VideoGame = VideoGame("Gears of War", "Epic Games", 8)
print(game.name) // "Gears of War"
print(game.publisher) // "Epic Games"
print(game.reviewScore) // 8
game.reviewScore = 7

How to deserialise a subclass in Firebase using getValue(Subclass.class)

I'm using the new firebase sdk for android and use the real database feature. When i use the getValue(simple.class) everything is fine. But when i want to parse a class which is a subclass, all the attribute of the mother class are null, and i have this type of error:
No setter/field for name found on class uk.edume.edumeapp.TestChild
public class TestChild extends TestMother {
private String childAttribute;
public String getChildAttribute() {
return childAttribute;
}
}
public class TestMother {
protected String motherAttribute;
protected String getMotherAttribute() {
return motherAttribute;
}
}
this function
snapshot.getValue(TestChild.class);
motherAttribute attribute is null, and I get
No setter/field for motherAttribute found on class uk.edume.edumeapp.TestChild
the Json that i parse is:
{
"childAttribute" : "attribute in child class",
"motherAttribute" : "attribute in mother class"
}
Firebaser here
This is a known bug in some versions of the Firebase Database SDK for Android: our serializer/deserializer only considers properties/fields on the declared class.
Serialization of inherited properties from the base class, is missing in the in releases 9.0 to 9.6 (iirc) of the Firebase Database SDK for Android. It was added back in versions since then.
Workaround
In the meantime you can use Jackson (which the Firebase 2.x SDKs used under the hood) to make the inheritance model work.
Update: here's a snippet of how you can read from JSON into your TestChild:
public class TestParent {
protected String parentAttribute;
public String getParentAttribute() {
return parentAttribute;
}
}
public class TestChild extends TestParent {
private String childAttribute;
public String getChildAttribute() {
return childAttribute;
}
}
You'll note that I made getParentAttribute() public, because only public fields/getters are considered. With that change, this JSON:
{
"childAttribute" : "child",
"parentAttribute" : "parent"
}
Becomes readable with:
ObjectMapper mapper = new ObjectMapper();
GenericTypeIndicator<Map<String,Object>> indicator = new GenericTypeIndicator<Map<String, Object>>() {};
TestChild value = mapper.convertValue(dataSnapshot.getValue(indicator), TestChild.class);
The GenericTypeIndicator is a bit weird, but luckily it's a magic incantation that can be copy/pasted.
This was apparently finally fixed in release 9.6.
Fixed an issue where passing a derived class to DatabaseReference#setValue() did not correctly save the properties from the superclass.
for:
No setter/field for motherAttribute found on class uk.edume.edumeapp.TestChild
put setter for TestChild class:
public class TestMother {
private String motherAttribute;
public String getMotherAttribute() {
return motherAttribute;
}
//set
public void setMotherAttribute(String motherAttribute) {
this.motherAttribute= motherAttribute;
}
}
Check this https://firebase.google.com/support/guides/firebase-android
it says
"If there is an extra property in your JSON that is not in your Java class, you will see this warning in the log files: W/ClassMapper: No setter/field for ignoreThisProperty found on class com.firebase.migrationguide.ChatMessage
"
Blockquote
You can get rid of this warning by putting an #IgnoreExtraProperties annotation on your class. If you want Firebase Database to behave as it did in the 2.x SDK and throw an exception if there are unknown properties, you can put a #ThrowOnExtraProperties annotation on your class.
Blockquote

Using Realm with Gson

I have json with field _id
String json = "{ _id : 1, name : 'Alex', role: 'admin' }"
In my Realm model I use #SerializedName attribute:
public class User extends RealmObject {
#SerializedName("_id")
#PrimaryKey
private int id;
private String name;
private String comment;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
}
If try save the json:
realm.createOrUpdateObjectFromJson(User.class, json)
field _id can't parsed and in database created record with id = 0
In docs using #SerializedName attribute
Gson gson = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}).create();
User user = gson.fromJson(json, User.class);
realm.beginTransaction();
realm.copyToRealmOrUpdate(user);
In this case json = "{ _id : 1, role: 'user' }" just remove user name from database, because default value for String is null.
So, probably I incorrectly using the attribute. How to consider the attribute when dealing with the methods of conservation of json (createOrUpdateObjectFromJson, etc)?
Why writing all these custom serializers when you can make Gson and
Realm work together with just ONE LINE OF CODE?
IMO, setting an exclusive strategy to every model we want to serialize is not the best approach. As you have realized, this approach requires writing a lot of boiler-plate code which is error prone and the worst of all, kills what Gson is about (which is making our lives less painful).
And since we're working-around incompatibilities, why don't you just make sure to pass an unmanged RealmObject to your Gson serializer?
Simplest solution ever (IMO)
Found here. Get a copy in memory of the managed RealmObject and pass it to Gson
new Gson().toJson(realm.copyFromRealm(managedModel));
And that's it! No more code to write!
Other good solutions and explanations are posted here.
You probably need a hybrid solution in order to make this work. The reason is that #SerializedName is a GSON annotation and not one that Realm knows about. So this means you have two choices as you already discovered:
1) Use GSON, which means the end result is an object with null as the default for name.
2) Use createOrUpdateObjectFromJson which means that _id will be ignored because Realm's JSON parser require a 1:1 mapping.
As neither solution will work correctly you can modify them in the following way:
1) With GSON, instead of doing copyToRealmOrUpdate you can manually search for the object and update the role:
realm.beginTransaction();
realm.where(User.class).equalTo("id", user.getId()).findFirst().setRole(user.getRole());
realm.commitTransaction();
2) Using just the pure JSON you can modify to match the expected format:
JSONObject obj = new JSONObject(json);
obj.put("id", obj.getString("_id"));
obj.remove("_id");
Realm has an issue tracking the request for custom mappings, but it has a low priority as that feature is usually better covered by frameworks such as GSON, Jacokson, etc. : https://github.com/realm/realm-java/issues/1470

Categories

Resources