6

我正在开发一个 Android 项目,目前正在尝试弄清楚如何JSON从我们API的 s 中反序列化一些包含引用循环的对象图,然后我可以对其进行操作并将其存储在数据库中。让我举个例子吧:

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

在这里,一个名为的人对象Bob是与人的朋友,而与人AliceAlice是朋友Bob。由于关系是递归的,因此不再Alice将 的好友关系Bob实现为完整的人对象,而仅id提供了他的关系。

您使用什么工具来执行上述步骤?我尝试用 Jackson 实现对象映射部分,但未能找到循环要求的解决方案。我发现一个关于这个主题的持续讨论提到了 JSOG,这可能会有所帮助,但我们的 API 是固定的,并且不符合 JSOG。

基本上我正在寻找的是类似 Android 的 RestKit(iOS 框架)。

4

3 回答 3

4

一旦 API 被修复,我会以这种方式实现它:

从数据库的角度来看,我有 2 个表 - UserTableRelationsTable来保持你朋友图的所有边缘:
在此处输入图像描述

想法是将用户保留在一个表中,并将它们的关系保留在关系表中。它还允许稍后在其之上添加一些额外的逻辑(例如,用户隐藏他的连接或阻止某人等 - 图表的任何可能的边缘)。此外,它还可以缓解循环引用的问题

作为从服务和解析 json 中检索数据的框架,我会使用Retrofit

首先,我要定义UserBaseUser类:

public class UserBase {
    public string id;
}

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

如您所见,其中friendsRetrofit用于从 JSON 中解析对象的对象列表,以及UserBase-我们将在进一步的步骤中从 SQLite 手动填充的列表。userFriends

现在,让我们定义一些帮助类来操作 DB:

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;
    }
}

现在我们需要为 RelatedTable 和 UserTable 提供 Dao:

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);
    ...
}

完成后,我们就可以实际实现这个接口了:

DefaultUserRelationDao班级:

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);
    }
}

DefaultUserDao类:

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;
    }
}

要创建我们的表,我们需要扩展SQLiteOpenHelperonCreate()实际创建表:

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);
    }
    ...
}

现在,我建议使用缓存定义所有可能的操作的 LocalStorage 接口:

  • 获取所有用户
  • 通过id获取用户
  • 添加用户
  • 添加用户之间的连接
  • 等等

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

它的实现:

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;
        }
    }
}

最后一步——它的实际用法:

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

注意!我在这里大量使用我的自定义类 SqlUtilities。不幸的是,它太大了,无法在此处发布,但只是一个示例,以提供一些想法 - 这是 getUsers(Cursor cursor) 在那里的外观:

.....
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; 
}
.....

我希望,你会原谅我跳过一些细节(特别是关于情况,当数据库必须更新时,当数据未满时,除了从缓存中获取它之外,你必须先从服务器检索它,然后将它加载到缓存等)。如果缺少任何关键部分 - 请在评论中发布,我很乐意更新帖子。

我希望,它会帮助你。

于 2016-01-24T09:30:37.730 回答
1

您可以查看JSON-RPC。这是一个很好的框架,支持JSON解析和复杂对象关系的对象映射。

于 2016-01-26T19:48:54.363 回答
0

我想说您正在尝试解决错误的问题,而真正的问题是您的数据表示已损坏。除了循环引用问题之外,它的效率也很低,因为每个朋友都会为每个友谊复制。最好像这样扁平化您的人员列表:

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

将列表存储在HashMap<Integer, Person>(或SparseArray<Person>)中。任务完成!

于 2016-01-26T08:48:22.657 回答