I have a high dimensional dataset in a JSON structure. I read them with the Volley library. I want to save this data in the database and access it. How can I do it?
JSON data:
{
"MyData": [
{
"food_id": "1",
"food_name": "Food 1",
"food_image": "imageurl",
"food_kcal": "32",
"food_url": "url",
"food_description": "desc",
"carb_percent": "72",
"protein_percent": "23",
"fat_percent": "4",
"units": [
{
"unit": "Unit A",
"amount": "735.00",
"calory": "75.757",
"calcium": "8.580",
"carbohydrt": "63.363",
"cholestrl": "63.0",
"fiber_td": "56.12",
"iron": "13.0474",
"lipid_tot": "13.01",
"potassium": "11.852",
"protein": "717.1925",
"sodium": "112.02",
"vit_a_iu": "110.7692",
"vit_c": "110.744"
},
{
"unit": "Unit C",
"amount": "32.00",
"calory": "23.757",
"calcium": "53.580",
"carbohydrt": "39.363",
"cholestrl": "39.0",
"fiber_td": "93.12",
"iron": "93.0474",
"lipid_tot": "93.01",
"potassium": "9.852",
"protein": "72.1925",
"sodium": "10.0882",
"vit_a_iu": "80.7692",
"vit_c": "80.744"
}
]
},
{
"food_id": "2",
"food_name": "Food 2",
"food_image": "imageurl",
"food_kcal": "50",
"food_url": "url",
"food_description": "desc",
"carb_percent": "25",
"protein_percent": "14",
"fat_percent": "8",
"units": [
{
"unit": "Unit A",
"amount": "25.00",
"calory": "25.757",
"calcium": "55.580",
"carbohydrt": "53.363",
"cholestrl": "53.0",
"fiber_td": "53.12",
"iron": "53.0474",
"lipid_tot": "53.01",
"potassium": "17.852",
"protein": "757.1925",
"sodium": "122.02",
"vit_a_iu": "10.7692",
"vit_c": "10.744"
},
{
"unit": "Unit C",
"amount": "2.00",
"calory": "2.757",
"calcium": "5.580",
"carbohydrt": "3.363",
"cholestrl": "3.0",
"fiber_td": "3.12",
"iron": "3.0474",
"lipid_tot": "3.01",
"potassium": "77.852",
"protein": "77.1925",
"sodium": "12.02",
"vit_a_iu": "0.7692",
"vit_c": "0.744"
},
{
"unit": "Unit G",
"amount": "1.00",
"calory": "2.1",
"calcium": "0.580",
"carbohydrt": "0.363",
"cholestrl": "0.0",
"fiber_td": "0.12",
"iron": "0.0474",
"lipid_tot": "0.01",
"potassium": "5.852",
"protein": "0.1925",
"sodium": "1.02",
"vit_a_iu": "0.7692",
"vit_c": "0.744"
}
]
}
]
}
For SQLite
The first thing that you have to do is determine the schema of the database.
Looking at your data you have a lots of MyData objects, each of which will have 0 or more Unit objects.
Thus you could have a table for the MyData objects and table for the Unit objects. Each Unit will have a parent MyData object. So in addition to the data for each Unit you could have a column that references (maps, associates) the MyData (probably it's food_id assuming that this uniquely identifies the MyData).
Using a shortened version of MyData (just the food_id, food_name) and a shortened version of Unit (unit, amount and calory) to demonstrate then you could have two classes as:-
class MyData {
public static final String TABLE_NAME = "_mydata";
public static final String COLUMN_FOOD_ID = "_food_id";
public static final String COLUMN_FOOD_NAME = "_food_name";
long food_id;
String food_name;
Unit[] units;
public MyData(long food_id, String food_name, Unit[] units) {
this.food_id = food_id;
this.food_name = food_name;
this.units = units;
}
}
and
class Unit {
public static final String TABLE_NAME = "_unit";
public static final String COLUMN_UNIT = "_unit";
public static final String COLUMN_AMOUNT = "_amount";
public static final String COLUMN_CALORY = "_calory";
public static final String COLUMN_FOOD_ID_PARENT ="parent_food_id";
String unit;
double amount;
double calory;
public Unit(String unit, double amount, double calory) {
this.unit = unit;
this.amount = amount;
this.calory = calory;
}
}
Note the constants that will be used for the tables i.e. the schema. These are placed into the classes for brevity, they could be coded elsewhere.
Note the additional column parent_food_id, this will be used to reference the parent food_id.
So the schema will(could) look like
In SQLite terms the SQL's (one for each table) that will create the above could be :-
CREATE TABLE _mydata(_food_id INTEGER PRIMARY KEY,_food_name TEXT)
, andCREATE TABLE _unit(_unit TEXT,_amount REAL,_calory REAL,parent_food_id INTEGER REFERENCES _mydata(_food_id) ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY(_unit,_amount,_calory))
Handling the Database
With Android and SQLite then the typical way is to utilise a DatabaseHelper that extends the SQLiteOpenHelper. SQLiteOpenHelpr requires 2 overidden methods onCreate and onUpgrae
Typically code for accessing the database such as adding, removing, updating, deleting or extracting data.
For the demonstration the Database will will just have methods for inserting data into the tables. Additionally a method that will take a JSON String (equivalent of a file) and populate the tables.
So the DatabaseHelper class could be :-
class DatabaseHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "my_database.db";
public static final int DATABASE_VERSION = 1;
private SQLiteDatabase db;
private static volatile DatabaseHelper instance = null;
private DatabaseHelper(Context context) {
super(context,DATABASE_NAME,null,DATABASE_VERSION);
db = this.getWritableDatabase();
}
/* Use a singleton approach */
public static DatabaseHelper getInstance(Context context) {
if (instance == null) {
instance = new DatabaseHelper(context);
}
return instance;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(
"CREATE TABLE IF NOT EXISTS " + MyData.TABLE_NAME + "(" +
MyData.COLUMN_FOOD_ID + " INTEGER PRIMARY KEY," +
MyData.COLUMN_FOOD_NAME + " TEXT" +
")"
);
db.execSQL(
"CREATE TABLE IF NOT EXISTS " + Unit.TABLE_NAME + "(" +
Unit.COLUMN_UNIT + " TEXT," +
Unit.COLUMN_AMOUNT + " REAL," +
Unit.COLUMN_CALORY + " REAL," +
Unit.COLUMN_FOOD_ID_PARENT +
/* Foreign Key Constraint to enforce referential integrity*/
" INTEGER REFERENCES " + MyData.TABLE_NAME + "(" + MyData.COLUMN_FOOD_ID + ") " +
/* These make maintaining referential integrity easier */
"ON DELETE CASCADE " +
"ON UPDATE CASCADE, " +
/* define the primary key */
" PRIMARY KEY(" +
Unit.COLUMN_UNIT + "," + Unit.COLUMN_AMOUNT + "," + Unit.COLUMN_CALORY +
")" +
")"
);
}
@Override
public void onUpgrade(SQLiteDatabase db, int i, int i1) {
/* not expecting to increase the database version so leave this to do nothing */
}
public long insertMyData(MyData myData) {
ContentValues cv = new ContentValues();
cv.put(MyData.COLUMN_FOOD_ID,myData.food_id);
cv.put(MyData.COLUMN_FOOD_NAME,myData.food_name);
return db.insert(MyData.TABLE_NAME,null,cv);
}
public long insertUnit(Unit unit, long parentId) {
ContentValues cv = new ContentValues();
cv.put(Unit.COLUMN_UNIT,unit.unit);
cv.put(Unit.COLUMN_AMOUNT,unit.amount);
cv.put(Unit.COLUMN_CALORY,unit.calory);
cv.put(Unit.COLUMN_FOOD_ID_PARENT,parentId);
return db.insert(Unit.TABLE_NAME,null,cv);
}
public void massInsert(String jsonString) {
MyData[] extracted = new Gson().fromJson(jsonString,MyData[].class);
db.beginTransaction();
for(MyData m: new Gson().fromJson(jsonString,MyData[].class)) {
long food_id = insertMyData(m);
if (food_id > 0) {
for(Unit u: m.units) {
insertUnit(u,food_id);
}
}
}
db.setTransactionSuccessful();
db.endTransaction();
}
}
Demonstration
Here's a demonstration of using the above in an activity, namely MainActivity:-
public class MainActivity extends AppCompatActivity {
MyData[] TESTDATA = {
new MyData(
1,
"Bat",
new Unit[]{
new Unit("Unit A",15.0000,32.4877372383),
new Unit("Unit C",110.0000,238.243404414),
new Unit("Unit F",1.0000,2.16584914922)
}
),
new MyData(
2,
"Another",
new Unit[]{
new Unit("Unit A",17.0000,3.4877372383),
new Unit("Unit C",10.0000,382.243404414),
new Unit("Unit F",3.0000,5.16584914922)
}
),
};
DatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String test = new Gson().toJson(TESTDATA);
Log.d("APPINFO","TESTDATA as JSON is\n\t" + test);
//MyData[] extracted = new Gson().fromJson(test,MyData[].class);
dbHelper = DatabaseHelper.getInstance(this); // Prepare to use the database
dbHelper.massInsert(test); // Actually use the database to insert the data
/* Example of extracting data using a JOIN to combine the MyData and the related Units */
Cursor csr = dbHelper.getWritableDatabase().query(
MyData.TABLE_NAME + " JOIN " + Unit.TABLE_NAME + " ON " + Unit.COLUMN_FOOD_ID_PARENT + "=" + MyData.COLUMN_FOOD_ID,
null,null,null,null,null,null
);
DatabaseUtils.dumpCursor(csr);
csr.close();
}
}
Results
When run the log includes :-
2022-01-08 07:32:14.666 D/APPINFO: TESTDATA as JSON is
[{"food_id":1,"food_name":"Bat","units":[{"amount":15.0,"calory":32.4877372383,"unit":"Unit A"},{"amount":110.0,"calory":238.243404414,"unit":"Unit C"},{"amount":1.0,"calory":2.16584914922,"unit":"Unit F"}]},{"food_id":2,"food_name":"Another","units":[{"amount":17.0,"calory":3.4877372383,"unit":"Unit A"},{"amount":10.0,"calory":382.243404414,"unit":"Unit C"},{"amount":3.0,"calory":5.16584914922,"unit":"Unit F"}]}]
and
2022-01-08 07:32:14.698 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@f302fce
2022-01-08 07:32:14.699 I/System.out: 0 {
2022-01-08 07:32:14.699 I/System.out: _food_id=1
2022-01-08 07:32:14.699 I/System.out: _food_name=Bat
2022-01-08 07:32:14.699 I/System.out: _unit=Unit A
2022-01-08 07:32:14.699 I/System.out: _amount=15
2022-01-08 07:32:14.699 I/System.out: _calory=32.4877
2022-01-08 07:32:14.699 I/System.out: parent_food_id=1
2022-01-08 07:32:14.699 I/System.out: }
2022-01-08 07:32:14.699 I/System.out: 1 {
2022-01-08 07:32:14.699 I/System.out: _food_id=1
2022-01-08 07:32:14.699 I/System.out: _food_name=Bat
2022-01-08 07:32:14.700 I/System.out: _unit=Unit C
2022-01-08 07:32:14.701 I/System.out: _amount=110
2022-01-08 07:32:14.701 I/System.out: _calory=238.243
2022-01-08 07:32:14.701 I/System.out: parent_food_id=1
2022-01-08 07:32:14.701 I/System.out: }
2022-01-08 07:32:14.701 I/System.out: 2 {
2022-01-08 07:32:14.701 I/System.out: _food_id=1
2022-01-08 07:32:14.701 I/System.out: _food_name=Bat
2022-01-08 07:32:14.701 I/System.out: _unit=Unit F
2022-01-08 07:32:14.701 I/System.out: _amount=1
2022-01-08 07:32:14.701 I/System.out: _calory=2.16585
2022-01-08 07:32:14.701 I/System.out: parent_food_id=1
2022-01-08 07:32:14.701 I/System.out: }
2022-01-08 07:32:14.702 I/System.out: 3 {
2022-01-08 07:32:14.702 I/System.out: _food_id=2
2022-01-08 07:32:14.702 I/System.out: _food_name=Another
2022-01-08 07:32:14.702 I/System.out: _unit=Unit A
2022-01-08 07:32:14.702 I/System.out: _amount=17
2022-01-08 07:32:14.702 I/System.out: _calory=3.48774
2022-01-08 07:32:14.702 I/System.out: parent_food_id=2
2022-01-08 07:32:14.702 I/System.out: }
2022-01-08 07:32:14.702 I/System.out: 4 {
2022-01-08 07:32:14.702 I/System.out: _food_id=2
2022-01-08 07:32:14.703 I/System.out: _food_name=Another
2022-01-08 07:32:14.703 I/System.out: _unit=Unit C
2022-01-08 07:32:14.703 I/System.out: _amount=10
2022-01-08 07:32:14.703 I/System.out: _calory=382.243
2022-01-08 07:32:14.703 I/System.out: parent_food_id=2
2022-01-08 07:32:14.703 I/System.out: }
2022-01-08 07:32:14.703 I/System.out: 5 {
2022-01-08 07:32:14.703 I/System.out: _food_id=2
2022-01-08 07:32:14.703 I/System.out: _food_name=Another
2022-01-08 07:32:14.703 I/System.out: _unit=Unit F
2022-01-08 07:32:14.703 I/System.out: _amount=3
2022-01-08 07:32:14.704 I/System.out: _calory=5.16585
2022-01-08 07:32:14.704 I/System.out: parent_food_id=2
2022-01-08 07:32:14.704 I/System.out: }
2022-01-08 07:32:14.704 I/System.out: <<<<<
Using Android Studio's App Inspection the database looks like :-
and