JSON 中处理循环引用的解析框架
Parsing framework that deals well with circular references in JSON
我正在研究一个 Android 项目,目前正试图弄清楚如何从我们的 API
中反序列化一些 JSON
,其中包括 引用循环 到一个对象图中,然后我可以对其进行操作并将其存储在数据库中。让我举个例子:
{
"id": "24",
"name": "Bob",
"friends": [
{
"id": "13",
"name": "Alice",
"friends": [
{
"id": "24" // and we have a circular reference
}
]
}
]
}
这里,一个名为 Bob
的人物对象是 Alice
的朋友,而 Alice
又是 Bob
的朋友。由于关系是递归的,Alice
与 Bob
的朋友关系不再作为完整的人对象实现,而只提供了他的 id
。
您使用什么工具来执行上述步骤?我尝试用 Jackson 实现对象映射部分,但未能找到满足循环要求的解决方案。我找到了一个可能有用的 ongoing discussion about this topic that mentions JSOG,但我们的 API 是固定的,不符合 JSOG。
基本上我正在寻找的是 Android.
的 RestKit(iOS 框架)之类的东西
一旦 API 修复,我将以这种方式实施它:
从数据库的角度来看,我有 2 个 tables - UserTable 和 RelationsTable 来保持你的所有边缘好友图:
即想法是让用户保持在 table 中,而他们的关系保持在关系 table 中。它还允许稍后在其之上添加一些额外的逻辑(例如,用户隐藏他的连接或阻止某人等 - 图形的任何可能边缘)。此外,它可以缓解循环引用的问题。
作为从服务检索数据和解析 json 的框架,我会使用 Retrofit。
首先,我定义 UserBase
和 User
classes:
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;
}
其中,如您所见,friends
是 Retrofit 的 UserBase
个对象的列表,用于解析 JSON 和 userFriends
中的对象 -列表,我们将在后续步骤中手动从 SQLite 中填充。
现在,让我们定义一些 help-classes 来与数据库一起操作:
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;
}
}
现在我们需要 Dao 的 RelatedTable 和 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);
...
}
一旦完成,我们就可以实际实现这个接口了:
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);
}
}
和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;
}
}
要创建我们的 tables,我们需要扩展 SQLiteOpenHelper
并在 onCreate()
中实际创建 tables:
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");
注意!我在这里大量使用我的自定义 class SqlUtilities。不幸的是,它太大了 post 它在这里,但只是一个例子来给出一些想法里面有什么 - 这是 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;
}
.....
我希望,你能原谅我跳过一些细节(特别是,关于案例,当数据库必须更新时,当数据未满时,除了从缓存中获取它,您必须先从服务器检索它,然后将其加载到缓存中,等等)。如果缺少任何关键部分 - 请在评论中post,我很乐意更新 post。
希望对你有所帮助
我会说你试图解决错误的问题,真正的问题是你的数据表示被破坏了。除了循环引用问题之外,它的效率也很低,因为每个朋友都会因为每个友谊而被复制。最好像这样压平你的人员名单:
[
{
"id": "13",
"name": "Alice",
"friends": ["24"]
},
{
"id": "24",
"name": "Bob",
"friends": ["13"]
}
]
将列表存储在HashMap<Integer, Person>
(或SparseArray<Person>
)中。任务完成!
你可以看看JSON-RPC。这是一个很好的框架,支持JSON复杂对象关系的解析和对象映射。
我正在研究一个 Android 项目,目前正试图弄清楚如何从我们的 API
中反序列化一些 JSON
,其中包括 引用循环 到一个对象图中,然后我可以对其进行操作并将其存储在数据库中。让我举个例子:
{
"id": "24",
"name": "Bob",
"friends": [
{
"id": "13",
"name": "Alice",
"friends": [
{
"id": "24" // and we have a circular reference
}
]
}
]
}
这里,一个名为 Bob
的人物对象是 Alice
的朋友,而 Alice
又是 Bob
的朋友。由于关系是递归的,Alice
与 Bob
的朋友关系不再作为完整的人对象实现,而只提供了他的 id
。
您使用什么工具来执行上述步骤?我尝试用 Jackson 实现对象映射部分,但未能找到满足循环要求的解决方案。我找到了一个可能有用的 ongoing discussion about this topic that mentions JSOG,但我们的 API 是固定的,不符合 JSOG。
基本上我正在寻找的是 Android.
的 RestKit(iOS 框架)之类的东西一旦 API 修复,我将以这种方式实施它:
从数据库的角度来看,我有 2 个 tables - UserTable 和 RelationsTable 来保持你的所有边缘好友图:
即想法是让用户保持在 table 中,而他们的关系保持在关系 table 中。它还允许稍后在其之上添加一些额外的逻辑(例如,用户隐藏他的连接或阻止某人等 - 图形的任何可能边缘)。此外,它可以缓解循环引用的问题。
作为从服务检索数据和解析 json 的框架,我会使用 Retrofit。
首先,我定义 UserBase
和 User
classes:
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;
}
其中,如您所见,friends
是 Retrofit 的 UserBase
个对象的列表,用于解析 JSON 和 userFriends
中的对象 -列表,我们将在后续步骤中手动从 SQLite 中填充。
现在,让我们定义一些 help-classes 来与数据库一起操作:
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;
}
}
现在我们需要 Dao 的 RelatedTable 和 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);
...
}
一旦完成,我们就可以实际实现这个接口了:
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);
}
}
和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;
}
}
要创建我们的 tables,我们需要扩展 SQLiteOpenHelper
并在 onCreate()
中实际创建 tables:
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");
注意!我在这里大量使用我的自定义 class SqlUtilities。不幸的是,它太大了 post 它在这里,但只是一个例子来给出一些想法里面有什么 - 这是 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;
}
.....
我希望,你能原谅我跳过一些细节(特别是,关于案例,当数据库必须更新时,当数据未满时,除了从缓存中获取它,您必须先从服务器检索它,然后将其加载到缓存中,等等)。如果缺少任何关键部分 - 请在评论中post,我很乐意更新 post。
希望对你有所帮助
我会说你试图解决错误的问题,真正的问题是你的数据表示被破坏了。除了循环引用问题之外,它的效率也很低,因为每个朋友都会因为每个友谊而被复制。最好像这样压平你的人员名单:
[
{
"id": "13",
"name": "Alice",
"friends": ["24"]
},
{
"id": "24",
"name": "Bob",
"friends": ["13"]
}
]
将列表存储在HashMap<Integer, Person>
(或SparseArray<Person>
)中。任务完成!
你可以看看JSON-RPC。这是一个很好的框架,支持JSON复杂对象关系的解析和对象映射。