Android Room Database: How to handle Arraylist in an Entity? - java
I just implemented Room for offline data saving. But in an Entity class, I am getting the following error:
Error:(27, 30) error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
And the class is as following:
#Entity(tableName = "firstPageData")
public class MainActivityData {
#PrimaryKey
private String userId;
#ColumnInfo(name = "item1_id")
private String itemOneId;
#ColumnInfo(name = "item2_id")
private String itemTwoId;
// THIS IS CAUSING THE ERROR... BASICALLY IT ISN'T READING ARRAYS
#ColumnInfo(name = "mylist_array")
private ArrayList<MyListItems> myListItems;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public ArrayList<MyListItems> getMyListItems() {
return myListItems;
}
public void setCheckListItems(ArrayList<MyListItems> myListItems) {
this.myListItems = myListItems;
}
}
So basically I want to save the ArrayList in the database but I was not able to find anything relevant to it. Can you guide me regarding how to save an Array using Room?
NOTE: MyListItems Pojo class contains 2 Strings (as of now)
Type Converter are made specifically for that. In your case, you can use code snippet given below to store data in DB.
public class Converters {
#TypeConverter
public static ArrayList<String> fromString(String value) {
Type listType = new TypeToken<ArrayList<String>>() {}.getType();
return new Gson().fromJson(value, listType);
}
#TypeConverter
public static String fromArrayList(ArrayList<String> list) {
Gson gson = new Gson();
String json = gson.toJson(list);
return json;
}
}
And mention this class in your Room DB like this
#Database (entities = {MainActivityData.class},version = 1)
#TypeConverters({Converters.class})
More info here
Option #1: Have MyListItems be an #Entity, as MainActivityData is. MyListItems would set up a #ForeignKey back to MainActivityData. In this case, though, MainActivityData cannot have private ArrayList<MyListItems> myListItems, as in Room, entities do not refer to other entities. A view model or similar POJO construct could have a MainActivityData and its associated ArrayList<MyListItems>, though.
Option #2: Set up a pair of #TypeConverter methods to convert ArrayList<MyListItems> to and from some basic type (e.g., a String, such as by using JSON as a storage format). Now, MainActivityData can have its ArrayList<MyListItems> directly. However, there will be no separate table for MyListItems, and so you cannot query on MyListItems very well.
Kotlin version for type converter:
class Converters {
#TypeConverter
fun listToJson(value: List<JobWorkHistory>?) = Gson().toJson(value)
#TypeConverter
fun jsonToList(value: String) = Gson().fromJson(value, Array<JobWorkHistory>::class.java).toList()
}
I Used JobWorkHistory object for my purpose, use the object of your own
#Database(entities = arrayOf(JobDetailFile::class, JobResponse::class), version = 1)
#TypeConverters(Converters::class)
abstract class MyRoomDataBase : RoomDatabase() {
abstract fun attachmentsDao(): AttachmentsDao
}
Native Kotlin version using Kotlin's serialization component – kotlinx.serialization.
Add the Kotlin serialization Gradle plugin and dependency to your build.gradle:
apply plugin: 'kotlinx-serialization'
dependencies {
...
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
}
Add the Type converters to your Converter class;
class Converters {
#TypeConverter
fun fromList(value : List<String>) = Json.encodeToString(value)
#TypeConverter
fun toList(value: String) = Json.decodeFromString<List<String>>(value)
}
Add your Converter class to your database class:
#TypeConverters(Converters::class)
abstract class YourDatabase: RoomDatabase() {...}
And you're done!
Extra resources:
Type Converters
Kotlin Serialization
Better version of List<String> converter
class StringListConverter {
#TypeConverter
fun fromString(stringListString: String): List<String> {
return stringListString.split(",").map { it }
}
#TypeConverter
fun toString(stringList: List<String>): String {
return stringList.joinToString(separator = ",")
}
}
Kotlin Answer
You need to do 3 things:
Create Converters class.
Add Converters class on Database.
Just define what you want to use in Entity class.
Usage example step by step:
Step 1 :
class Converters {
#TypeConverter
fun listToJsonString(value: List<YourModel>?): String = Gson().toJson(value)
#TypeConverter
fun jsonStringToList(value: String) = Gson().fromJson(value, Array<YourModel>::class.java).toList()
}
Step 2 :
#Database(entities = [YourEntity::class], version = 1)
#TypeConverters(Converters::class)
abstract class YourDatabase : RoomDatabase() {
abstract fun yourDao(): YourDao
}
Step 3 :
Note: You do not need to call functions of Converter which are listToJsonString() and jsonStringToList(). They are using in background by Room.
#Entity(tableName = "example_database_table")
data class YourEntity(
#PrimaryKey(autoGenerate = true) val id: Long = 0,
#ColumnInfo(name = "your_model_list") var yourModelList: List<YourModel>,
)
I would personally advise against #TypeConverters/serializations, since they break the database's normal forms compliance.
For this particular case it might be worth defining a relationship using the #Relation annotation, which allows to query nested entities into a single object without the added complexity of declaring a #ForeignKey and writing all the SQL queries manually:
#Entity
public class MainActivityData {
#PrimaryKey
private String userId;
private String itemOneId;
private String itemTwoId;
}
#Entity
public class MyListItem {
#PrimaryKey
public int id;
public String ownerUserId;
public String text;
}
/* This is the class we use to define our relationship,
which will also be used to return our query results.
Note that it is not defined as an #Entity */
public class DataWithItems {
#Embedded public MainActivityData data;
#Relation(
parentColumn = "userId"
entityColumn = "ownerUserId"
)
public List<MyListItem> myListItems;
}
/* This is the DAO interface where we define the queries.
Even though it looks like a single SELECT, Room performs
two, therefore the #Transaction annotation is required */
#Dao
public interface ListItemsDao {
#Transaction
#Query("SELECT * FROM MainActivityData")
public List<DataWithItems> getAllData();
}
Aside from this 1-N example, it is possible to define 1-1 and N-M relationships as well.
This is how i handle List conversion
public class GenreConverter {
#TypeConverter
public List<Integer> gettingListFromString(String genreIds) {
List<Integer> list = new ArrayList<>();
String[] array = genreIds.split(",");
for (String s : array) {
if (!s.isEmpty()) {
list.add(Integer.parseInt(s));
}
}
return list;
}
#TypeConverter
public String writingStringFromList(List<Integer> list) {
String genreIds = "";
for (int i : list) {
genreIds += "," + i;
}
return genreIds;
}}
And then on the database i do as shown below
#Database(entities = {MovieEntry.class}, version = 1)
#TypeConverters(GenreConverter.class)
And below is a kotlin implementation of the same;
class GenreConverter {
#TypeConverter
fun gettingListFromString(genreIds: String): List<Int> {
val list = mutableListOf<Int>()
val array = genreIds.split(",".toRegex()).dropLastWhile {
it.isEmpty()
}.toTypedArray()
for (s in array) {
if (s.isNotEmpty()) {
list.add(s.toInt())
}
}
return list
}
#TypeConverter
fun writingStringFromList(list: List<Int>): String {
var genreIds=""
for (i in list) genreIds += ",$i"
return genreIds
}}
Had the same error message as described above.
I would like to add: if you get this error message in a #Query, you should add #TypeConverters above the #Query annotation.
Example:
#TypeConverters(DateConverter.class)
#Query("update myTable set myDate=:myDate where id = :myId")
void updateStats(int myId, Date myDate);
....
public class DateConverter {
#TypeConverter
public static Date toDate(Long timestamp) {
return timestamp == null ? null : new Date(timestamp);
}
#TypeConverter
public static Long toTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
This answer uses Kotin to split by comma and construct the comma delineated string. The comma needs to go at the end of all but the last element, so this will handle single element lists as well.
object StringListConverter {
#TypeConverter
#JvmStatic
fun toList(strings: String): List<String> {
val list = mutableListOf<String>()
val array = strings.split(",")
for (s in array) {
list.add(s)
}
return list
}
#TypeConverter
#JvmStatic
fun toString(strings: List<String>): String {
var result = ""
strings.forEachIndexed { index, element ->
result += element
if(index != (strings.size-1)){
result += ","
}
}
return result
}
}
in my case problem was generic type
base on this answer
https://stackoverflow.com/a/48480257/3675925
use List instead of ArrayList
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
class IntArrayListConverter {
#TypeConverter
fun fromString(value: String): List<Int> {
val type = object: TypeToken<List<Int>>() {}.type
return Gson().fromJson(value, type)
}
#TypeConverter
fun fromArrayList(list: List<Int>): String {
val type = object: TypeToken<List<Int>>() {}.type
return Gson().toJson(list, type)
}
}
it doesn't need add #TypeConverters(IntArrayListConverter::class) to query in dao class nor fields in Entity class
and just add #TypeConverters(IntArrayListConverter::class) to database class
#Database(entities = [MyEntity::class], version = 1, exportSchema = false)
#TypeConverters(IntArrayListConverter::class)
abstract class MyDatabase : RoomDatabase() {
All above answers are for list of strings. But below helps you to write converter for list of objects.
Just in place of "YourClassName", add your Object class.
#TypeConverter
public String fromValuesToList(ArrayList<**YourClassName**> value) {
if (value== null) {
return (null);
}
Gson gson = new Gson();
Type type = new TypeToken<ArrayList<**YourClassName**>>() {}.getType();
return gson.toJson(value, type);
}
#TypeConverter
public ArrayList<**YourClassName**> toOptionValuesList(String value) {
if (value== null) {
return (null);
}
Gson gson = new Gson();
Type type = new TypeToken<List<**YourClassName**>>() {
}.getType();
return gson.fromJson(value, type);
}
When we are using TypeConverters then datatype should be return type of TypeConverter method.
For example: TypeConverter method returns a string, then adding table column should be of type string.
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(#NonNull SupportSQLiteDatabase database) {
// Since we didn't alter the table, there's nothing else to do here.
database.execSQL("ALTER TABLE "+ Collection.TABLE_STATUS + " ADD COLUMN deviceType TEXT;");
database.execSQL("ALTER TABLE "+ Collection.TABLE_STATUS + " ADD COLUMN inboxType TEXT;");
}
};
Adding #TypeConverters with the converter class as params
to Database & to the Dao class, made my queries work
Json conversions don't scale well in terms of memory allocation.I'd rather go for something similar to responses above with some nullability.
class Converters {
#TypeConverter
fun stringAsStringList(strings: String?): List<String> {
val list = mutableListOf<String>()
strings
?.split(",")
?.forEach {
list.add(it)
}
return list
}
#TypeConverter
fun stringListAsString(strings: List<String>?): String {
var result = ""
strings?.forEach { element ->
result += "$element,"
}
return result.removeSuffix(",")
}
}
For simple data types the above can be used, otherwise for complex datatypes Room provides Embedded
Here is the example for adding the customObject types to Room DB table.
https://mobikul.com/insert-custom-list-and-get-that-list-in-room-database-using-typeconverter/
Adding a type converter was easy, I just needed a method that could turn the list of objects into a string, and a method that could do the reverse. I used gson for this.
public class Converters {
#TypeConverter
public static String MyListItemListToString(List<MyListitem> list) {
Gson gson = new Gson();
return gson.toJson(list);
}
#TypeConverter
public static List<Integer> stringToMyListItemList(#Nullable String data) {
if (data == null) {
return Collections.emptyList();
}
Type listType = new TypeToken<List<MyListItem>>() {}.getType();
Gson gson = new Gson();
return gson.fromJson(data, listType);
}
}
I then added an annotation to the field in the Entity:
#TypeConverters(Converters.class)
public final ArrayList<MyListItem> myListItems;
#Query("SELECT * FROM business_table")
abstract List<DatabaseModels.Business> getBusinessInternal();
#Transaction #Query("SELECT * FROM business_table")
public ArrayList<DatabaseModels.Business> getBusiness(){
return new ArrayList<>(getBusinessInternal());
}
All answers above correct. Yes, if you REALLY need store array of something into one SQLite field TypeConverter is a solution.
And I used the accepted answer in my projects.
But don't do it!!!
If you need store array in Entity in 90% cases you need to create one-to-many or many-to-many relationships.
Otherwise, your next SQL query for select something with key inside this array will be absolutely hell...
Example:
Object foo comes as json: [{id: 1, name: "abs"}, {id:2, name: "cde"}
Object bar: [{id, 1, foos: [1, 2], {...}]
So don't make entity like:
#Entity....
data class bar(
...
val foos: ArrayList<Int>)
Make like next:
#Entity(tablename="bar_foo", primaryKeys=["fooId", "barId"])
data class barFoo(val barId: Int, val fooId: Int)
And sore your foos:[] as records in this table.
I wanted to store a List containing photos URI in ROOM database . Because of special characters I was getting this error localized in my converter class, TypeConverters.class:
com.google.gson.stream.MalformedJsonException: Unterminated array at line 1 column 8 path $[1]
There was a problem in my simple converter for arrayList of String, in fact it was :
public static List<String> fromString(String value) {
Type listType = new TypeToken<ArrayList<String>>() {
}.getType();
return new Gson().fromJson(value, listType);
}
#TypeConverter
public static String fromArrayList(List<String> list) {
Gson gson = new Gson();
return gson.toJson(list);
}
By taking #Derrick Njeru comment back, I changed it for that it could take in consideration a String as "https://images.app.goo.gl/jwhkhzhZVWrceQV67" like this :
#TypeConverter
public List<String> gettingListFromString(String genreIds) {
List<String> list = new ArrayList<>();
String[] array = genreIds.split(",");
for (String s : array) {
if (!s.isEmpty()) {
list.add(s);
}
}
return list;
}
#TypeConverter
public String writingStringFromList(List<String> list) {
String genreIds = "";
for (String i : list) {
genreIds += "," + i;
}
return genreIds;
}
Use official solution from room, #Embedded annotation :
#Embedded(prefix = "mylist_array") private ArrayList<MyListItems> myListItems
Related
Android Room - error: Cannot figure out how to save this field into database
Detailed log error: Cannot figure out how to save this field into database. You can consider adding a type converter for it. private final java.util.Date mTime = null; I have an entity with a field as var mStartTime : Date = Date() // java.util.Date Why cant Room persist Date objects? What can be best converter for Date?
Date is exactly the example given in https://developer.android.com/training/data-storage/room/referencing-data. For example, if we want to persist instances of Date, we can write the following TypeConverter to store the equivalent Unix timestamp in the database: public class Converters { #TypeConverter public static Date fromTimestamp(Long value) { return value == null ? null : new Date(value); } #TypeConverter public static Long dateToTimestamp(Date date) { return date == null ? null : date.getTime(); } } The preceding example defines 2 functions, one that converts a Date object to a Long object and another that performs the inverse conversion, from Long to Date. Since Room already knows how to persist Long objects, it can use this converter to persist values of type Date. Next, you add the #TypeConverters annotation to the AppDatabase class so that Room can use the converter that you've defined for each entity and DAO in that AppDatabase: AppDatabase.java #Database(entities = {User.class}, version = 1) #TypeConverters({Converters.class}) public abstract class AppDatabase extends RoomDatabase { public abstract UserDao userDao(); } A side note: java.util.Date is considered to be badly designed (and java.util.Calendar is much worse). If you have any non-trivial date-time logic and can get away with API level 26 (Java 8 on desktop), it's generally better to use java.time package. And if you can't, see https://github.com/JakeWharton/ThreeTenABP for a backport.
// Java code will not convert to Kotlin very // well so here is the Kotlin: Converter // class public class Converters { #TypeConverter fun fromTimestamp( value: Long?) : java.sql.Date { return java.sql.Date(value ?: 0) } #TypeConverter fun dateToTimestamp(date :java.sql.Date?) :Long { return date?.getTime() ?: 0 } // Here is the type converters example in // Kotlin #Database(entities = [DbNasaPictures::class], version = 2) #TypeConverters(Converters::class) abstract class PicturesDatabase: RoomDatabase() {
All above answers is for list of strings.But below helps you to find converter for list of your objects. Just in place of "YourClassName" ,add your Object class. #TypeConverter public String fromValuesToList(ArrayList<**YourClassName**> value) { if (value== null) { return (null); } Gson gson = new Gson(); Type type = new TypeToken<ArrayList<**YourClassName**>>() {}.getType(); return gson.toJson(value, type); } #TypeConverter public ArrayList<**YourClassName**> toOptionValuesList(String value) { if (value== null) { return (null); } Gson gson = new Gson(); Type type = new TypeToken<List<**YourClassName**>>() { }.getType(); return gson.fromJson(value, type); }
In Kotlin, Need to add #TypeConverters in Two files Database class in which extend by RoomDatabase #Database(entities = [ToDo::class], version = 1, exportSchema = false) #TypeConverters(DateConverter::class) abstract class AppDatabase : RoomDatabase() { abstract fun todoDao(): ToDoDao companion object { #Volatile private var instance: AppDatabase? = null fun getDatabase(context: Context): AppDatabase = instance ?: synchronized(this) { instance ?: buildDatabase(context).also { instance = it } } private fun buildDatabase(appContext: Context) = Room.databaseBuilder(appContext, AppDatabase::class.java, "todo") .fallbackToDestructiveMigration() .build() } } Data class in which #Entity declared and need to add DateConverter class for Date #Entity(tableName = "todo") data class ToDo( #PrimaryKey val id: Int, val title: String, val description: String, val time: String, val date: String, val types: Int, #TypeConverters(DateConverter::class) val date_time: Date, val created: String ) DateConverter class import androidx.room.TypeConverter import java.util.* class DateConverter { #TypeConverter fun toDate(timestamp: Long): Date { return Date(timestamp) } #TypeConverter fun toTimestamp(date: Date): Long { return date.time } }
How to store a List<String> from spring JPA to text[] array in database
I am trying to write an Java- Array List into postgres database. But i am not able to DO, i don't get error as well. I am trying to post data "skill":["java,dotnet"] using postman tool but on Post nothing happens and i don't see any data in db.I also don't get any error in my console.please help me out on this i m wondering how to do this I am able to read Array data from database but cannot write /* * Spring Entity: */ #Column(name="skill" , columnDefinition = "text[]") #Convert(converter =ListToStringConverter.class) private List<String> skill=new ArrayList<>(); ublic List<String> getSkill() { return skill; } public void setSkill(List<String> skill) { this.skill= skill; } #Converter public class ListToStringConverter implements AttributeConverter<List<String>, String> { #Override public String convertToDatabaseColumn(List<String> attribute) { if (attribute == null || attribute.isEmpty()) { return ""; } return StringUtils.join(attribute, ","); } #Override public List<String> convertToEntityAttribute(String dbData) { if (dbData == null || dbData.trim().length() == 0) { return new ArrayList<String>(); } String[] data = dbData.split(","); return Arrays.asList(data); } } Controller Code: This is my Controller code to create a object in database and i use a interface service that is call from controller to Save to db-Postgres which is a JPA repo #RequestMapping(value = "/reqCreate", method = RequestMethod.POST) public ResponseEntity<Requirement> addRequirement(#RequestBody Requirement requirement) { reqService.save(requirement); return new ResponseEntity<Requirement>(requirement, HttpStatus.CREATED); } Service: public void save(Requirement r) { reqRepo.save(r); }
The columnDefinition seems to be wrong. You want to convert Collection into String, but you still declare column of array type. Replace "text[]" with "text" and check if it works?
Read-only field in GSON serialization [duplicate]
I'm trying to learn Gson and I'm struggling with field exclusion. Here are my classes public class Student { private Long id; private String firstName = "Philip"; private String middleName = "J."; private String initials = "P.F"; private String lastName = "Fry"; private Country country; private Country countryOfBirth; } public class Country { private Long id; private String name; private Object other; } I can use the GsonBuilder and add an ExclusionStrategy for a field name like firstName or country but I can't seem to manage to exclude properties of certain fields like country.name. Using the method public boolean shouldSkipField(FieldAttributes fa), FieldAttributes doesn't contain enough information to match the field with a filter like country.name. P.S: I want to avoid annotations since I want to improve on this and use RegEx to filter fields out. Edit: I'm trying to see if it's possible to emulate the behavior of Struts2 JSON plugin using Gson <interceptor-ref name="json"> <param name="enableSMD">true</param> <param name="excludeProperties"> login.password, studentList.*\.sin </param> </interceptor-ref> Edit: I reopened the question with the following addition: I added a second field with the same type to futher clarify this problem. Basically I want to exclude country.name but not countrOfBirth.name. I also don't want to exclude Country as a type. So the types are the same it's the actual place in the object graph that I want to pinpoint and exclude.
Any fields you don't want serialized in general you should use the "transient" modifier, and this also applies to json serializers (at least it does to a few that I have used, including gson). If you don't want name to show up in the serialized json give it a transient keyword, eg: private transient String name; More details in the Gson documentation
Nishant provided a good solution, but there's an easier way. Simply mark the desired fields with the #Expose annotation, such as: #Expose private Long id; Leave out any fields that you do not want to serialize. Then just create your Gson object this way: Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
So, you want to exclude firstName and country.name. Here is what your ExclusionStrategy should look like public class TestExclStrat implements ExclusionStrategy { public boolean shouldSkipClass(Class<?> arg0) { return false; } public boolean shouldSkipField(FieldAttributes f) { return (f.getDeclaringClass() == Student.class && f.getName().equals("firstName"))|| (f.getDeclaringClass() == Country.class && f.getName().equals("name")); } } If you see closely it returns true for Student.firstName and Country.name, which is what you want to exclude. You need to apply this ExclusionStrategy like this, Gson gson = new GsonBuilder() .setExclusionStrategies(new TestExclStrat()) //.serializeNulls() <-- uncomment to serialize NULL fields as well .create(); Student src = new Student(); String json = gson.toJson(src); System.out.println(json); This returns: { "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91}} I assume the country object is initialized with id = 91L in student class. You may get fancy. For example, you do not want to serialize any field that contains "name" string in its name. Do this: public boolean shouldSkipField(FieldAttributes f) { return f.getName().toLowerCase().contains("name"); } This will return: { "initials": "P.F", "country": { "id": 91 }} EDIT: Added more info as requested. This ExclusionStrategy will do the thing, but you need to pass "Fully Qualified Field Name". See below: public class TestExclStrat implements ExclusionStrategy { private Class<?> c; private String fieldName; public TestExclStrat(String fqfn) throws SecurityException, NoSuchFieldException, ClassNotFoundException { this.c = Class.forName(fqfn.substring(0, fqfn.lastIndexOf("."))); this.fieldName = fqfn.substring(fqfn.lastIndexOf(".")+1); } public boolean shouldSkipClass(Class<?> arg0) { return false; } public boolean shouldSkipField(FieldAttributes f) { return (f.getDeclaringClass() == c && f.getName().equals(fieldName)); } } Here is how we can use it generically. Gson gson = new GsonBuilder() .setExclusionStrategies(new TestExclStrat("in.naishe.test.Country.name")) //.serializeNulls() .create(); Student src = new Student(); String json = gson.toJson(src); System.out.println(json); It returns: { "firstName": "Philip" , "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91 }}
After reading all available answers I found out, that most flexible, in my case, was to use custom #Exclude annotation. So, I implemented simple strategy for this (I didn't want to mark all fields using #Expose nor I wanted to use transient which conflicted with in app Serializable serialization) : Annotation: #Retention(RetentionPolicy.RUNTIME) #Target(ElementType.FIELD) public #interface Exclude { } Strategy: public class AnnotationExclusionStrategy implements ExclusionStrategy { #Override public boolean shouldSkipField(FieldAttributes f) { return f.getAnnotation(Exclude.class) != null; } #Override public boolean shouldSkipClass(Class<?> clazz) { return false; } } Usage: new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();
I ran into this issue, in which I had a small number of fields I wanted to exclude only from serialization, so I developed a fairly simple solution that uses Gson's #Expose annotation with custom exclusion strategies. The only built-in way to use #Expose is by setting GsonBuilder.excludeFieldsWithoutExposeAnnotation(), but as the name indicates, fields without an explicit #Expose are ignored. As I only had a few fields I wanted to exclude, I found the prospect of adding the annotation to every field very cumbersome. I effectively wanted the inverse, in which everything was included unless I explicitly used #Expose to exclude it. I used the following exclusion strategies to accomplish this: new GsonBuilder() .addSerializationExclusionStrategy(new ExclusionStrategy() { #Override public boolean shouldSkipField(FieldAttributes fieldAttributes) { final Expose expose = fieldAttributes.getAnnotation(Expose.class); return expose != null && !expose.serialize(); } #Override public boolean shouldSkipClass(Class<?> aClass) { return false; } }) .addDeserializationExclusionStrategy(new ExclusionStrategy() { #Override public boolean shouldSkipField(FieldAttributes fieldAttributes) { final Expose expose = fieldAttributes.getAnnotation(Expose.class); return expose != null && !expose.deserialize(); } #Override public boolean shouldSkipClass(Class<?> aClass) { return false; } }) .create(); Now I can easily exclude a few fields with #Expose(serialize = false) or #Expose(deserialize = false) annotations (note that the default value for both #Expose attributes is true). You can of course use #Expose(serialize = false, deserialize = false), but that is more concisely accomplished by declaring the field transient instead (which does still take effect with these custom exclusion strategies).
You can explore the json tree with gson. Try something like this : gson.toJsonTree(student).getAsJsonObject() .get("country").getAsJsonObject().remove("name"); You can add some properties also : gson.toJsonTree(student).getAsJsonObject().addProperty("isGoodStudent", false); Tested with gson 2.2.4.
I came up with a class factory to support this functionality. Pass in any combination of either fields or classes you want to exclude. public class GsonFactory { public static Gson build(final List<String> fieldExclusions, final List<Class<?>> classExclusions) { GsonBuilder b = new GsonBuilder(); b.addSerializationExclusionStrategy(new ExclusionStrategy() { #Override public boolean shouldSkipField(FieldAttributes f) { return fieldExclusions == null ? false : fieldExclusions.contains(f.getName()); } #Override public boolean shouldSkipClass(Class<?> clazz) { return classExclusions == null ? false : classExclusions.contains(clazz); } }); return b.create(); } } To use, create two lists (each is optional), and create your GSON object: static { List<String> fieldExclusions = new ArrayList<String>(); fieldExclusions.add("id"); fieldExclusions.add("provider"); fieldExclusions.add("products"); List<Class<?>> classExclusions = new ArrayList<Class<?>>(); classExclusions.add(Product.class); GSON = GsonFactory.build(null, classExclusions); } private static final Gson GSON; public String getSomeJson(){ List<Provider> list = getEntitiesFromDatabase(); return GSON.toJson(list); }
I solved this problem with custom annotations. This is my "SkipSerialisation" Annotation class: #Target (ElementType.FIELD) public #interface SkipSerialisation { } and this is my GsonBuilder: gsonBuilder.addSerializationExclusionStrategy(new ExclusionStrategy() { #Override public boolean shouldSkipField (FieldAttributes f) { return f.getAnnotation(SkipSerialisation.class) != null; } #Override public boolean shouldSkipClass (Class<?> clazz) { return false; } }); Example : public class User implements Serializable { public String firstName; public String lastName; #SkipSerialisation public String email; }
Kotlin's #Transientannotation also does the trick apparently. data class Json( #field:SerializedName("serialized_field_1") val field1: String, #field:SerializedName("serialized_field_2") val field2: String, #Transient val field3: String ) Output: {"serialized_field_1":"VALUE1","serialized_field_2":"VALUE2"}
Or can say whats fields not will expose with: Gson gson = gsonBuilder.excludeFieldsWithModifiers(Modifier.TRANSIENT).create(); on your class on attribute: private **transient** boolean nameAttribute;
I used this strategy: i excluded every field which is not marked with #SerializedName annotation, i.e.: public class Dummy { #SerializedName("VisibleValue") final String visibleValue; final String hiddenValue; public Dummy(String visibleValue, String hiddenValue) { this.visibleValue = visibleValue; this.hiddenValue = hiddenValue; } } public class SerializedNameOnlyStrategy implements ExclusionStrategy { #Override public boolean shouldSkipField(FieldAttributes f) { return f.getAnnotation(SerializedName.class) == null; } #Override public boolean shouldSkipClass(Class<?> clazz) { return false; } } Gson gson = new GsonBuilder() .setExclusionStrategies(new SerializedNameOnlyStrategy()) .create(); Dummy dummy = new Dummy("I will see this","I will not see this"); String json = gson.toJson(dummy); It returns {"VisibleValue":"I will see this"}
Another approach (especially useful if you need to make a decision to exclude a field at runtime) is to register a TypeAdapter with your gson instance. Example below: Gson gson = new GsonBuilder() .registerTypeAdapter(BloodPressurePost.class, new BloodPressurePostSerializer()) In the case below, the server would expect one of two values but since they were both ints then gson would serialize them both. My goal was to omit any value that is zero (or less) from the json that is posted to the server. public class BloodPressurePostSerializer implements JsonSerializer<BloodPressurePost> { #Override public JsonElement serialize(BloodPressurePost src, Type typeOfSrc, JsonSerializationContext context) { final JsonObject jsonObject = new JsonObject(); if (src.systolic > 0) { jsonObject.addProperty("systolic", src.systolic); } if (src.diastolic > 0) { jsonObject.addProperty("diastolic", src.diastolic); } jsonObject.addProperty("units", src.units); return jsonObject; } }
I'm working just by putting the #Expose annotation, here my version that I use compile 'com.squareup.retrofit2:retrofit:2.0.2' compile 'com.squareup.retrofit2:converter-gson:2.0.2' In Model class: #Expose int number; public class AdapterRestApi { In the Adapter class: public EndPointsApi connectRestApi() { OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(90000, TimeUnit.SECONDS) .readTimeout(90000,TimeUnit.SECONDS).build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(ConstantRestApi.ROOT_URL) .addConverterFactory(GsonConverterFactory.create()) .client(client) .build(); return retrofit.create (EndPointsApi.class); }
I have Kotlin version #Retention(AnnotationRetention.RUNTIME) #Target(AnnotationTarget.FIELD) internal annotation class JsonSkip class SkipFieldsStrategy : ExclusionStrategy { override fun shouldSkipClass(clazz: Class<*>): Boolean { return false } override fun shouldSkipField(f: FieldAttributes): Boolean { return f.getAnnotation(JsonSkip::class.java) != null } } and how You can add this to Retrofit GSONConverterFactory: val gson = GsonBuilder() .setExclusionStrategies(SkipFieldsStrategy()) //.serializeNulls() //.setDateFormat(DateFormat.LONG) //.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) //.setPrettyPrinting() //.registerTypeAdapter(Id.class, IdTypeAdapter()) .create() return GsonConverterFactory.create(gson)
This what I always use: The default behaviour implemented in Gson is that null object fields are ignored. Means Gson object does not serialize fields with null values to JSON. If a field in a Java object is null, Gson excludes it. You can use this function to convert some object to null or well set by your own /** * convert object to json */ public String toJson(Object obj) { // Convert emtpy string and objects to null so we don't serialze them setEmtpyStringsAndObjectsToNull(obj); return gson.toJson(obj); } /** * Sets all empty strings and objects (all fields null) including sets to null. * * #param obj any object */ public void setEmtpyStringsAndObjectsToNull(Object obj) { for (Field field : obj.getClass().getDeclaredFields()) { field.setAccessible(true); try { Object fieldObj = field.get(obj); if (fieldObj != null) { Class fieldType = field.getType(); if (fieldType.isAssignableFrom(String.class)) { if(fieldObj.equals("")) { field.set(obj, null); } } else if (fieldType.isAssignableFrom(Set.class)) { for (Object item : (Set) fieldObj) { setEmtpyStringsAndObjectsToNull(item); } boolean setFielToNull = true; for (Object item : (Set) field.get(obj)) { if(item != null) { setFielToNull = false; break; } } if(setFielToNull) { setFieldToNull(obj, field); } } else if (!isPrimitiveOrWrapper(fieldType)) { setEmtpyStringsAndObjectsToNull(fieldObj); boolean setFielToNull = true; for (Field f : fieldObj.getClass().getDeclaredFields()) { f.setAccessible(true); if(f.get(fieldObj) != null) { setFielToNull = false; break; } } if(setFielToNull) { setFieldToNull(obj, field); } } } } catch (IllegalAccessException e) { System.err.println("Error while setting empty string or object to null: " + e.getMessage()); } } } private void setFieldToNull(Object obj, Field field) throws IllegalAccessException { if(!Modifier.isFinal(field.getModifiers())) { field.set(obj, null); } } private boolean isPrimitiveOrWrapper(Class fieldType) { return fieldType.isPrimitive() || fieldType.isAssignableFrom(Integer.class) || fieldType.isAssignableFrom(Boolean.class) || fieldType.isAssignableFrom(Byte.class) || fieldType.isAssignableFrom(Character.class) || fieldType.isAssignableFrom(Float.class) || fieldType.isAssignableFrom(Long.class) || fieldType.isAssignableFrom(Double.class) || fieldType.isAssignableFrom(Short.class); }
in kotlin can use #Transient to ignore the field... eg. data class MyClass{ #Transient var myVar: Boolean //.... }
Use different DTO for cached object. For example, you can create UserCached class and keep there only fields you need. After that, create mapper to map objects back & forth. Mapstruct is good for that. Such approach solves the problem, decouples your application, and makes changes in your primary DTO more safe to make.
Setting Default value to a variable when deserializing using gson
I am trying to convert JSON to Java object. When a certain value of a pair is null, it should be set with some default value. Here is my POJO: public class Student { String rollNo; String name; String contact; String school; public String getRollNo() { return rollNo; } public void setRollNo(String rollNo) { this.rollNo = rollNo; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSchool() { return school; } public void setSchool(String school) { this.school = school; } } Example JSON object: { "rollNo":"123", "name":"Tony", "school":null } So if school is null, I should make this into a default value, such as "school":"XXX". How can I configure this with Gson while deserializing the objects?
If the null is in the JSON, Gson is going to override any defaults you might set in the POJO. You could go to the trouble of creating a custom deserializer, but that might be overkill in this case. I think the easiest (and, arguably best given your use case) thing to do is the equivalent of Lazy Loading. For example: private static final String DEFAULT_SCHOOL = "ABC Elementary"; public String getSchool() { if (school == null) school == DEFAULT_SCHOOL; return school; } public void setSchool(String school) { if (school == null) this.school = DEFAULT_SCHOOL; else this.school = school; } Note: The big problem with this solution is that in order to change the Defaults, you have to change the code. If you want the default value to be customizable, you should go with the custom deserializer as linked above.
I think that the way to do this is to either write your no-args constructor to fill in default values, or use a custom instance creator. The deserializer should then replace the default values for all attributes in the JSON object being deserialized.
I was having the same issue, until I found this great solution. For reference, you can create a post-processing class: interface PostProcessable { fun gsonPostProcess() } class PostProcessingEnabler : TypeAdapterFactory { fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T> { val delegate = gson.getDelegateAdapter(this, type) return object : TypeAdapter<T>() { #Throws(IOException::class) fun write(out: JsonWriter, value: T) { delegate.write(out, value) } #Throws(IOException::class) fun read(`in`: JsonReader): T { val obj = delegate.read(`in`) if (obj is PostProcessable) { (obj as PostProcessable).gsonPostProcess() } return obj } } } } Register it like this: GsonBuilder().registerTypeAdapterFactory(PostProcessingEnabler()) Implement it on your model: class MyClass : Serializable, PostProcessable { // All your variable data override fun gsonPostProcess() { // All your post processing logic you like on your object // set default value for example } } And finally use it when converting json string: var myObject = myGson.fromJson(myObjectJson, MyClass::class) Or using retrofit2: val api = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory( GsonConverterFactory.create( GsonBuilder().registerTypeAdapterFactory( GsonPostProcessingEnabler() ).create() ) ) .client(OkHttpClient.Builder().build()) .build() .create(AccountApi::class.java)
You can simply make a universal function that checks for null model.SchoolName= stringNullChecker(model.SchoolName); public static String stringNullChecker(String val) { if (null == val) val = ""; return val; }
jackson delay deserializing field
I have a class like this: public class DeserializedHeader int typeToClassId; Object obj I know what type of object obj is based on the typeToClassId, which is unfortunately only known at runtime. I want to parse obj out based on typeToClassId - what's the best approach here? Annotations seem like they're out, and something based on ObjectMapper seems right, but I'm having trouble figuring out what the best approach is likely to be. Something along the lines of Class clazz = lookUpClassBasedOnId(typeToClassId) objectMapper.readValue(obj, clazz) Obviously, this doesn't work since obj is already deserialized... but could I do this in 2 steps somehow, perhaps with convertValue?
This is really complex and painful problem. I do not know any sophisticated and elegant solution, but I can share with you my idea which I developed. I have created example program which help me to show you how you can solve your problem. At the beginning I have created two simple POJO classes: class Product { private String name; // getters/setters/toString } and class Entity { private long id; // getters/setters/toString } Example input JSON for those classes could look like this. For Product class: { "typeToClassId" : 33, "obj" : { "name" : "Computer" } } and for Entity class: { "typeToClassId" : 45, "obj" : { "id" : 10 } } The main functionality which we want to use is "partial serializing/deserializing". To do this we will enable FAIL_ON_UNKNOWN_PROPERTIES feature on ObjectMapper. Now we have to create two classes which define typeToClassId and obj properties. class HeaderType { private int typeToClassId; public int getTypeToClassId() { return typeToClassId; } public void setTypeToClassId(int typeToClassId) { this.typeToClassId = typeToClassId; } #Override public String toString() { return "HeaderType [typeToClassId=" + typeToClassId + "]"; } } class HeaderObject<T> { private T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } #Override public String toString() { return "HeaderObject [obj=" + obj + "]"; } } And, finally source code which can parse JSON: // Simple binding Map<Integer, Class<?>> classResolverMap = new HashMap<Integer, Class<?>>(); classResolverMap.put(33, Product.class); classResolverMap.put(45, Entity.class); ObjectMapper mapper = new ObjectMapper(); mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); String json = "{...}"; // Parse type HeaderType headerType = mapper.readValue(json, HeaderType.class); // Retrieve class by integer value Class<?> clazz = classResolverMap.get(headerType.getTypeToClassId()); // Create dynamic type JavaType type = mapper.getTypeFactory().constructParametricType(HeaderObject.class, clazz); // Parse object HeaderObject<?> headerObject = (HeaderObject<?>) mapper.readValue(json, type); // Get the object Object result = headerObject.getObj(); System.out.println(result); Helpful links: How To Convert Java Map To / From JSON (Jackson). java jackson parse object containing a generic type object.