A framework that works well with JSON circular links

I am working on an Android project, and I'm currently trying to figure out how to deserialize some JSON from our API , which includes reference loops in an object graph, which I can then manipulate and store in the database. Let me give an example:

 { "id": "24", "name": "Bob", "friends": [ { "id": "13", "name": "Alice", "friends": [ { "id": "24" // and we have a circular reference } ] } ] } 

Here, a person named Bob is friends with Alice , and Alice in turn, is friends with Bob . Since the relation is recursive, the relation of Alice s friends to Bob no longer implemented as a full-face object, but only its id provided.

What tools do you use to complete the above steps? I tried to implement part of object mapping using Jackson, but could not find a solution for the loop requirement. I found an ongoing discussion about this topic that mentions JSOGs that might be useful, but our APIs are fixed, not JSOG compatible.

Basically I am looking for something like RestKit (iOS framework) for Android.

+6
source share
3 answers

Once the API is fixed, I would execute it as follows:

From the DB point of view, I would have 2 tables - UserTable and Relative table , to save all the faces of your friends graph:
enter image description here

those. the idea is to keep users in the same table and their relationships in the relationship table . It also allows you to add some additional logic on top of it later (for example, the user hides his connection or blocks someone, etc. - any possible edges of the graph). In addition, it allows you to mitigate problems using circular references .

I would use Retrofit as the basis for extracting data from services and parsing jsons.

First, I would define the UserBase and User classes:

 public class UserBase { public string id; } public final class User extends UserBase { public string name; public List<UserBase> friends; // user "real" friends, not just ids, fills from SQLite public List<User> userFriends; } 

where, as you can see, friends is a list of UserBase objects for Upgrade to analyze an object from JSON and userFriends is a list that we will fill out from SQLite manually in the next steps.

Now we define some auxiliary classes for working with the database:

 public interface Dao<TItem> { void add(List<TItem> items); void removeAll(); List<TItem> getAll(); } .... public abstract class AbstractDao<TItem> implements Dao<TItem> { protected final SQLiteDatabase database; protected final SqlUtilities sqlUtilities; public AbstractDao(SQLiteDatabase database, SqlUtilities sqlUtilities) { this.database = database; this.sqlUtilities = sqlUtilities; } } 

Now we need Dao for RelatedTable and for UserTable:

 public class UserRelation { public String mainUserId; public String relatedUserId; } ... public interface UserRelationDao extends Dao<UserRelation> { ... List<User> getFriends(String userId); ... } ... public interface UserDao extends Dao<User> { ... void addWithIgnore(List<TItem> items); void update(List<TItem> items); void upsert(List<TItem> items); User getById(String userId); ... } 

Once this is done, we can actually implement these interfaces:

DefaultUserRelationDao class:

 public class DefaultUserRelationDao extends AbstractDao<UserRelation> implements UserRelationDao { static final String MAIN_USER_COLUMN = "mainuser"; static final String RELATED_USER_COLUMN = "relateduser"; private static final String[] COLUMN_NAMES = new String[]{ MAIN_USER_COLUMN, RELATED_USER_COLUMN, }; private static final String[] COLUMN_TYPES = new String[]{ "TEXT", "TEXT", }; private static final String TABLE = "userrelation"; static final String CREATE_TABLE = SqlUtilities.getCreateStatement(TABLE, COLUMN_NAMES, COLUMN_TYPES); static final String ALL_CONNECTED_USERS = "SELECT " + Joiner.on(",").join(DefaultUserDao.COLUMN_NAMES) + " FROM " + UserTable.TABLE_NAME + "," + TABLE + " WHERE " + RELATED_USER_COLUMN + "=" + DefaultUserDao.USER_ID_COLUMN; public DefaultUserRelationDao(SQLiteDatabase database, SqlUtilities sqlUtilities) { super(database, sqlUtilities); } @Override public void add(List<UserRelation> userRelations) { try { database.beginTransaction(); ContentValues contentValues = new ContentValues(); for (UserRelation relation : userRelations) { sqlUtilities.setValuesForUsersRelation(contentValues, relation); database.insertOrThrow(TABLE, null, contentValues); } database.setTransactionSuccessful(); } finally { database.endTransaction(); } } @Override public List<User> getFriends(String userId) { Cursor cursor = database.rawQuery(ALL_CONNECTED_USERS, new String[]{userId}); return sqlUtilities.getConnectedUsers(cursor); } } 

and DefaultUserDao class:

 public final class DefaultUserDao extends AbstractUDao<User> implements UserDao { public static final String USER_ID_COLUMN = "userid"; static final String USER_NAME_COLUMN = "username"; public static final String[] COLUMN_NAMES = new String[]{ USER_ID_COLUMN, USER_NAME_COLUMN, }; private static final String TABLE = "users"; private static final String SELECT_BY_ID = SqlUtilities.getSelectWhereStatement(TABLE, COLUMN_NAMES, new String[]{ USER_ID_COLUMN }); static final String CREATE_TABLE = SqlUtilities.getCreateStatement(TABLE, COLUMN_NAMES, COLUMN_TYPES); public DefaultUserDao(SQLiteDatabase database, SqlUtilities sqlUtilities) { super(database, sqlUtilities); } @Override public void add(List<User> users) { try { database.beginTransaction(); ContentValues contentValues = new ContentValues(); for (User user : users) { sqlUtilities.setValuesForUser(contentValues, user); database.insertOrThrow(UserTable.TABLE_NAME, null, contentValues); } database.setTransactionSuccessful(); } finally { database.endTransaction(); } } @Override public User getById(String userId) { return getUserBySingleColumn(SELECT_BY_ID, userId); } ..... private User getUserBySingleColumn(String selectStatement, String value) { Cursor cursor = database.rawQuery(selectStatement, new String[]{value}); List<User> users = sqlUtilities.getUsers(cursor); return (users.size() != 0) ? users.get(0) : null; } } 

To create our tables, we need to expand SQLiteOpenHelper and actually create tables in onCreate() :

 public final class DatabaseHelper extends SQLiteOpenHelper { static final String DATABASE_NAME = "mysuper.db"; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, SCHEMA_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(DefaultUserDao.CREATE_TABLE); db.execSQL(DefaultUserRelationDao.CREATE_TABLE); } ... } 

Now I would suggest defining the LocalStorage interface with all possible cache actions:

  • get all users
  • get user by id
  • add users
  • add connection between users
  • and etc.

     public interface LocalStorage { User getUserById(String userId); void addUsers(List<User> users); .... } 

and its implementation:

 public final class SqlLocalStorage implements LocalStorage { private UserDao userDao; private UserRelationDao userRelationDao; private SQLiteDatabase database; private final Object initializeLock = new Object(); private volatile boolean isInitialized = false; private SqlUtilities sqlUtilities; // there database is // SQLiteOpenHelper helper = new DatabaseHelper(context); // database = helper.getWritableDatabase(); public SqlLocalStorage(SQLiteDatabase database, SqlUtilities sqlUtilities) { this.database = database; this.sqlUtilities = sqlUtilities; } @Override public User getUserById(String userId) { initialize(); User user = userDao.getById(userId); if (user == null) { return null; } List<User> relatedUsers = userRelationDao.getFriends(userId); user.userFriends = relaterUsers; return user; } @Override public void addUsers(List<User> users) { initialize(); for (User user : users) { for (UserBase friend : user) { UserRelation userRelation = new UserRelation(); userRelation.mainUserId = user.id; userRelation.relatedUserId = friend.id; UserRelation userRelationMutual = new UserRelation(); userRelationMutual.mainUserId = friend.id; userRelationMutual.relatedUserId = user.id; userRelationDao.add(userRelation); userRelationMutual.add(userRelation) } } userDao.addWithIgnore(users); } void initialize() { if (isInitialized) { return; } synchronized (initializeLock) { if (isInitialized) { return; } Log.d(LOG_TAG, "Opens database"); userDao = new DefaultUserDao(database, sqlUtilities); userRelationDao = new DefaultUserRelationDao(database, sqlUtilities); isInitialized = true; } } } 

The final step is to actually use it:

 //somewhere in non-UI thread List<User> users = dataSource.getUsers(); localStorage.addUsers(users); final User userBob = localStorage.getUserById("42"); 

NB! I heavily use my custom SqlUtilities class here. Unfortunately, it is too large to post it here, but just an example to give some ideas on what's inside is what getUsers (cursor) looks like:

 ..... public List<User> getUsers(Cursor cursor) { ArrayList<User> users = new ArrayList<>(); try { while (cursor.moveToNext()) { users.add(getUser(cursor)); } } finally { cursor.close(); } return users; } private User getUser(Cursor cursor) { User user = new User(cursor.getString(0)); user.FullName = cursor.getString(1); .... return user; } ..... 

I hope you forgive me to skip some details (especially regarding the case when it is necessary to update the database when the data is not full and, in addition, to get it from the cache, you need to restore it first from the server, and then load it into the cache, etc.) .d.). If any important part is missing, please post it in the comments and I will be happy to update the message.

Hope this helps you.

+4
source

You can look at JSON-RPC . This is a good structure that supports JSON analysis and object mapping of complex object relationships.

+1
source

I would say that you are trying to solve the wrong problem, and the real problem is that your data representation is broken. Like the problem of circular refs, it is also ineffective in that each friend is duplicated for each friendship. It’s better to smooth the list of such people:

 [ { "id": "13", "name": "Alice", "friends": ["24"] }, { "id": "24", "name": "Bob", "friends": ["13"] } ] 

Save the list to HashMap<Integer, Person> (or SparseArray<Person> ). The task is completed!

0
source

All Articles