Home > Software engineering >  Deserialize json of recursive nested objects
Deserialize json of recursive nested objects

Time:05-26

I am trying to deserialize json like this using gson and then insert it into DB using Room but unsuccessfully:

[
  {
    "id": 1,
    "name": "category1",
    "categories": [
      {
        "id": 2,
        "name": "category2",
        "count": 321
      },
      {
        "id": 3,
        "name": "category3",
        "categories": [
          {
            "id": 4,
            "name": "category4",
            "count": 20
          },
          {
            "id": 5,
            "name": "category5",
            "count": 205
          },
          {
            "id": 6,
            "name": "category6",
            "count": 85
          }
        ]
      }
    ]
  }
]

Category object should contain:

  • id (unique, required)
  • name (required)
  • parent_id
  • count (content size if no child items)

Please help me to recursively parse this and insert into the db.

CodePudding user response:

What you need to do is have classes that reflect the JSON so that you can then extract the data. From those classes you can then insert the data.

Inspecting the json you have provided then you have:-

  1. an array of what could be MasterCategories, which has
  2. an array of Categories, each of which can have
  3. an array of Categories.

However, you appear to want to save everything according to :-

Category object should contain: id (unique, required) name (required) parent_id count (content size if no child items)

as a Category.

So with that in mind the main class would be Category (into which you want to store all 3 types (MainCategory, Category and the categories within a Category))

So first a MainCategory class:-

class MasterCategory {
   private long id;
   private String name;
   private List<Category> categories;

   public long getId() {
      return id;
   }

   public void setId(long id) {
      this.id = id;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public List<Category> getCategories() {
      return categories;
   }

   public void setCategories(List<Category> categories) {
      this.categories = categories;
   }
}

and then second a Category class which will also be the class used to define the table in the database. This could be:-

@Entity
class Category {
    @PrimaryKey
    private Long id = null;
    private String name;
    private long count;
    private Long parent = null;
    @Ignore
    private List<Category> categories = null;

    Category(){}
    @Ignore
    Category(Long id, String name, long count,Long parent, List<Category> categories) {
        this.id = id;
        this.name = name;
        this.count = count;
        this.parent = parent;
        this.categories = categories;
    }
    @Ignore
    Category(Long id, String name, long count, Long parent) {
        this.id = id;
        this.name = name;
        this.count = count;
        this.parent = parent;
        this.categories = null;
    }

    public long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }

    public List<Category> getCategories() {
        return categories;
    }

    public void setCategories(List<Category> categories) {
        this.categories = categories;
    }

    public Long getParent() {
        return parent;
    }

    public void setParent(Long parent) {
        this.parent = parent;
    }
}
  • you may wish to study this and reference the appropriate annotations

To support database access you have an @Dao annotated class CategoryDao e.g. (sufficient to demonstrate i.ee. store and retrieve):-

@Dao
abstract class CategoryDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract long insert(Category category);
    @Query("SELECT * FROM category")
    abstract List<Category> getAllCategories();
}

To combine all of the above, from a database aspect, you have an @Database annotated class TheDatabase e.g. :-

@Database(entities = {Category.class}, exportSchema = false, version = TheDatabase.DATABASE_VERSION)
abstract class TheDatabase extends RoomDatabase {
   public static final int DATABASE_VERSION = 1;
   public static final String DATABASE_NAME = "the_database.db";

   abstract CategoryDao getCategoryDao();

   private volatile static TheDatabase instance = null;
   public static TheDatabase getInstance(Context context) {
      if (instance == null) {
         instance = Room.databaseBuilder(context,TheDatabase.class,DATABASE_NAME)
                 .allowMainThreadQueries()
                 .build();
      }
      return instance;
   }
}
  • note that for convenience and brevity .allowMainThreadQueries has been coded (so the main thread can be used to demonstrate)

Last to actually demonstrate an activity that will take your JSON, store all three types (Master,Category and Categories) as a row, with the appropriate parent (i.e. Categories will have the respective MasterCategory id as the parent, categories within a Category will have the Category's id as the parent).

As MasterCategory has no count the count will be 0.

For the demo the gson library as per implementation 'com.google.code.gson:gson:2.9.0' has been used. This allows the pretty simple getMasterCategoryListFromJSON method, which converts the jSON to a MasterCategory array.

The other core method is insertCategoriesFromMasterCategoryArray. This takes a MasterCategory[] and inserts Category rows for all three levels/types. It returns a long[] of the inserted id's OR if an insert was IGNORED if there was a conflict (duplicate) then it will store -1 to indicate.

So MainActivity :-

public class MainActivity extends AppCompatActivity {

    String base = "["  
            "  {"  
            " id: 1,"  
            "    \"name\": \"category1\","  
            "    \"categories\": ["  
            "      {"  
            "        \"id\": 2,"  
            "        \"name\": \"category2\","  
            "        \"count\": 321"  
            "      },"  
            "      {"  
            "        \"id\": 3,"  
            "        \"name\": \"category3\","  
            "        \"categories\": ["  
            "          {"  
            "            \"id\": 4,"  
            "            \"name\": \"category4\","  
            "            \"count\": 20"  
            "          },"  
            "          {"  
            "            \"id\": 5,"  
            "            \"name\": \"category5\","  
            "            \"count\": 205"  
            "          },"  
            "          {"  
            "            \"id\": 6,"  
            "            \"name\": \"category6\","  
            "            \"count\": 85"  
            "          }"  
            "        ]"  
            "      }"  
            "    ]"  
            "  }"  
            "]";
    TheDatabase db;
    CategoryDao dao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        db = TheDatabase.getInstance(this);
        dao = db.getCategoryDao();

        MasterCategory[] mlist = getMasterCategoryListFromJSON(base);
        for (MasterCategory m: mlist) {
            Log.d("CATINFO","ID="   m.getId()   " NAME="   m.getName()   " it has "   m.getCategories().size()   " categories" );
        }
        long[] insertResult = insertCategoriesFromMasterCategoryArray(getMasterCategoryListFromJSON(base));
        for(Category c: dao.getAllCategories()) {
            Log.d("CATINFO", "ID = "   c.getId()   " NAME = "   c.getName()   " COUNT = "   c.getCount()   " PARENT = "   c.getParent());
        }
    }

    private MasterCategory[] getMasterCategoryListFromJSON(String json) {
        return new Gson().fromJson(json,MasterCategory[].class);
    }

    public long[] insertCategoriesFromMasterCategoryArray(MasterCategory[] mclist) {
        ArrayList<Long> rv = new ArrayList<>();
        Long currentMasterId = 0L;
        Long currentCategoryId = 0L;
        for (MasterCategory mc: mclist) {
            currentMasterId = dao.insert(new Category(mc.getId(),mc.getName(),0,null));
            rv.add(currentMasterId);
            for (Category c: mc.getCategories()) {
                c.setParent(currentMasterId);
                currentCategoryId = dao.insert(new Category(c.getId(),c.getName(),c.getCount(),c.getParent()));
                rv.add(currentCategoryId);
                if (c.getCategories() != null) {
                    for (Category sc : c.getCategories()) {
                        rv.add(dao.insert(new Category(sc.getId(), sc.getName(), sc.getCount(), currentCategoryId)));
                    }
                }
            }
        }
        long[] actualReturnValue = new long[rv.size()];
        for (int i = 0; i < rv.size(); i  ) {
            actualReturnValue[i] = rv.get(i);
        }
        return actualReturnValue;
    }
}

This, when run (it's only a demo and therefore intended to be run once) it outputs the following to the log:-

D/CATINFO: ID=1 NAME=category1 it has 2 categories


D/CATINFO: ID = 1 NAME = category1 COUNT = 0 PARENT = null
D/CATINFO: ID = 2 NAME = category2 COUNT = 321 PARENT = 1
D/CATINFO: ID = 3 NAME = category3 COUNT = 0 PARENT = 1
D/CATINFO: ID = 4 NAME = category4 COUNT = 20 PARENT = 3
D/CATINFO: ID = 5 NAME = category5 COUNT = 205 PARENT = 3
D/CATINFO: ID = 6 NAME = category6 COUNT = 85 PARENT = 3

Using App Inspection then the database is :-

enter image description here

  • Related