krhitesh
krhitesh

Reputation: 787

Failed to serialize object (using Room DB) to json string using Gson

I have a class Item (see below) in which I have used some Room db annotations. It also has a nested class named ItemInfo. Both of these classes have an empty constructor.

The problem is that when I try to serialize an object of Item class, app crashes with the following error:

E/AndroidRuntime: FATAL EXCEPTION: main
              Process: com.android.carrymates, PID: 18526
              java.lang.SecurityException: Can not make a java.lang.reflect.Method constructor accessible
                  at java.lang.reflect.AccessibleObject.setAccessible0(AccessibleObject.java:133)
                  at java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:119)
                  at com.google.gson.internal.reflect.PreJava9ReflectionAccessor.makeAccessible(PreJava9ReflectionAccessor.java:31)
                  at com.google.gson.internal.ConstructorConstructor.newDefaultConstructor(ConstructorConstructor.java:103)
                  at com.google.gson.internal.ConstructorConstructor.get(ConstructorConstructor.java:85)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:101)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.internal.bind.ArrayTypeAdapter$1.create(ArrayTypeAdapter.java:48)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.Gson.toJson(Gson.java:696)
                  at com.google.gson.Gson.toJson(Gson.java:683)
                  at com.google.gson.Gson.toJson(Gson.java:638)
                  at com.google.gson.Gson.toJson(Gson.java:618)
                  ... more log (irrelevant to question asked)

Item.java

@Entity(tableName = "items", indices = {@Index(value = {"id"}, unique = true), @Index(value = {"owner", "type"})})
public class Item {

    @PrimaryKey
    @NonNull
    String id="";

   //...rest fields are int, boolean and String only

    @Embedded
    ItemInfo itemInfo; // see ItemInfo


    public Item() {

   }


    // ...getters and setters

    @IgnoreExtraProperties
    public static class ItemInfo {

        //...fields are int, boolean and String only

        public ItemInfo() {

        }

        //...getters and setters
    }
}

My guess is that Room DB annotations are adding at least one object of type java.lang.reflect.Method which Gson is unable to serialize.

Below is the code I am using to serialize Item object to json string, where item is a object of class Item with non-null values of fields of type String and ItemInfo.

Gson gson = new Gson();
String result = gson.toJson(item); // crash begins from here

How do I address this problem? I expect at least a workaround solution.

Upvotes: 5

Views: 1800

Answers (5)

Denis Zavedeev
Denis Zavedeev

Reputation: 8297

Disclaimer:

I'm not aware of what RoomDB does with @Entity classes (though it looks like RoomDB uses subclasses rather than classes written by you).

Also I run the test on JVM

But I can suggest you to use @Expose:

public class GsonTest {

  private static class SampleModel {
    @Expose
    private int i;

    private Method method;

    @Expose
    private Nested nested = new Nested();
  }

  private static class Nested {
    @Expose
    private String a = "my string";
  }

  @Test
  public void failsWithMethodField() throws Exception {
    assertThrows(Exception.class, () -> {
      SampleModel sampleModel = new SampleModel();
      sampleModel.i = 10;
      sampleModel.method = Object.class.getDeclaredMethod("equals", Object.class);
      Gson gson = new Gson();
      gson.toJson(sampleModel);
    });
  }

  @Test
  public void withExposedDoesNotFail() {
    assertDoesNotThrow(() -> {
      SampleModel sampleModel = new SampleModel();
      sampleModel.method = Object.class.getDeclaredMethod("equals", Object.class);
      Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
      String json = gson.toJson(sampleModel);
      System.out.println(json); // {"i":0,"nested":{"a":"my string"}}
    });
  }
}

The essential part is to configure Gson with excludeFieldsWithoutExposeAnnotation option:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

Then mark all fields that should be used when serializing and deserializing with @Expose annotation.

Upvotes: 1

Martin Zeitler
Martin Zeitler

Reputation: 76689

do not nest these classes, in order to use the @Embedded annotation:

@Entity(
    tableName = "items",
    indices = {
        @Index(value = {"id"}, unique = true), 
        @Index(value = {"owner", "type"})
    }
)
public class Item {

    @PrimaryKey
    @ColumnInfo(name = "id")
    String id = null;

    @Embedded
    ItemInfo itemInfo;

    public Item() {

    }
}

@Entity(tableName = "item_info")
public class ItemInfo {

    public ItemInfo() {

    }

    ...
}

also see this answer, concerning GSON exclusion strategies (which might be required for itemInfo)

or simply add these fields directly into class Item, in order to serialize them all at once -

in order not to add more complexity than required, which only cause issues down the road.

Upvotes: 0

agravat.in
agravat.in

Reputation: 573

normally i get output when i try this,

public class Car { 

    public String brand = null;

    public int    doors = 0;

}


Car car = new Car();

car.brand = "Rover";

car.doors = 5;

Gson gson = new Gson();

String json = gson.toJson(car);

checkout this : http://tutorials.jenkov.com/java-json/gson.html

Upvotes: 0

Hovanes Mosoyan
Hovanes Mosoyan

Reputation: 760

Try this please: this code snipped is working properly

import android.arch.persistence.room.Embedded;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.Index;
import android.arch.persistence.room.PrimaryKey;
import android.support.annotation.NonNull;

import com.google.firebase.database.IgnoreExtraProperties;

@Entity(tableName = "items", indices = {@Index(value = {"id"}, unique = true), @Index(value = {"owner", "type"})})
public class Item {

    @PrimaryKey
    @NonNull
    String id="";

    //...rest fields are int, boolean and String only

    @Embedded
    ItemInfo itemInfo; // see ItemInfo


    public Item() {

    }


    // ...getters and setters

    @IgnoreExtraProperties
    public static class ItemInfo {

        //...fields are int, boolean and String only

        public ItemInfo() {

        }

        int prop1;
        String id="";
        //...getters and setters
    }
}

note the dependencies snipped too please

implementation 'com.google.firebase:firebase-core:16.0.3'
implementation "com.google.firebase:firebase-database:16.0.1"

// Arch
implementation "android.arch.core:runtime:1.1.1"
implementation "android.arch.core:common:1.1.1"

implementation 'android.arch.persistence.room:runtime:1.1.1';
annotationProcessor 'android.arch.persistence.room:compiler:1.1.1';

and the implementation:

        Item item = new Item();

        item.id = "Rover";
        item.itemInfo = new Item.ItemInfo();
        item.itemInfo.id = "asd";
        item.itemInfo.prop1 = 1;

        Gson gson = new Gson();

        String json = gson.toJson(item); // here json ={"id":"Rover","itemInfo":{"id":"asd","prop1":1}}

Upvotes: 0

Oleg Sokolov
Oleg Sokolov

Reputation: 1143

I can suggest you use different objects for different goals (storing in a Room db and serializing to json).

You need to have an interface of your Item entity:

public interface Item {

    int getId();

    //other fields
}

Then you need a specific implementation for a Room db entity. What you actually already have, but need make same refactoring:

@Entity(tableName = "items", indices = {@Index(value = {"id"}, unique = true), @Index(value = {"owner", "type"})})
public class RoomItem implements Item {

    @PrimaryKey
    @NonNull
    private int id;

    //other fields

    public RoomItem() {
    }

    public RoomItem(Item item) {
        id = item.getId();
    }

    @Override
    public int getId() {
        return 0;
    }

    @Override
    public void setId(int id) {
        this.id = id;
    }

    //other getters and setters
}

Plus you need to get rid of the inner static class ItemInfo and make it in a separate .java file.

And finally, you need a specific implementation for a Gson entity:

public class GsonItem implements Item {

    private final int id;

    public GsonItem(Item origin) {
        id = origin.getId();
    }

    @Override
    public int getId() {
        return id;
    }
}

In this case, you will able to use it like this without any issues:

Gson gson = new Gson();
String result = gson.toJson(new GsonItem(item));

Yes, this approach leads you to write a little bit more code, but lack of unexpected issues like yours definitely cost the effort!

Upvotes: 0

Related Questions