2016-01-21 2 views
6

Я работаю на Android проект и в настоящее время пытаюсь выяснить, как десериализации некоторых JSON от наших 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 об этой теме, которая упоминает JSOG, что может быть полезно, но наши API-интерфейсы исправлены, а не JSOG-совместимые.

В основном, я ищу что-то вроде RestKit (iOS framework) для Android.

+0

Я могу построить вам метод для анализа идентификатора, имени, друзей и возврата его в виде списка или любой структуры данных для дальнейшего процесса, это поможет? –

ответ

4

После API фиксирована, я бы реализовать таким образом:

С DB точки зрения, я бы 2 таблицы - UserTable и RelationsTable держать все ребра ваших друзей график:
enter image description here

Т.е. Идея состоит в том, чтобы сохранить пользователей в одной таблице и их отношениях в таблице отношений. Это позволяет также добавить некоторую дополнительную логику поверх нее позже (например, пользователь скрывает свое соединение или блокирует кого-то и т. Д. - любые возможные края графика). Кроме того, позволяет устранить проблемы с круговыми ссылками.

В качестве основы для извлечения данных из службы & parse jsons, я бы использовал Retrofit.

Во-первых, я бы определить UserBase и User классы:

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 список UserBase объектов для Retrofit разобрать объект из JSON и userFriends - список, которые мы будем заполнять из SQLite вручную в дальнейших шагах.

Теперь давайте определим некоторые справки-классы для работы с БД:

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 класс:

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

Чтобы создать наши таблицы, нам нужно расширить SQLiteOpenHelper и 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); 
    } 
    ... 
} 

Теперь я хотел бы предложить, чтобы определить LocalStorage интерфейс со всеми возможные действия с кешем:

  • получить все пользователи
  • Пользователь
  • както ид
  • добавить пользователей
  • добавить соединение между пользователями
  • т.д.

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

NB! Я сильно использую здесь свой пользовательский класс SqlUtilities. К сожалению, это слишком большой, чтобы разместить его здесь, но только один пример, чтобы дать некоторые идеи, что внутри - вот как getUsers (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; 
} 
..... 

Я надеюсь, что вы простите мне пропуск некоторых подробности (особенно в отношении случая, когда необходимо обновить БД, когда данные не заполнены и, кроме того, чтобы получить его из кеша, сначала необходимо извлечь его с сервера, а затем загрузить в кеш и т. д.). Если какая-либо важная часть отсутствует, пожалуйста, разместите ее в комментариях, и я буду рад обновить сообщение.

Надеюсь, это поможет вам.

+1

Спасибо за отзывы, ваши идеи многое помогли! Тем не менее, ваш ответ пропускает часть разбора моего вопроса, как обрабатывать циклы в JSON (используя фреймворк или собственный код), вот чего меня больше всего интересует. Есть идеи? – RaffAl

+2

@Bearwithme на самом деле, это не так :-) Retrofit анализирует ваш JSON сам по себе. Просто сохраните свойства вашего 'User', который вы хотите разобрать, как« public »и с теми же именами, что и в JSON, и фреймворк автоматически выполнит свою работу –

+2

, т.е., как вы можете видеть, я разбираю список друзей как список объектов класса 'UserBase' (ids) и не углубляться в рекурсию. –

0

Я бы сказал, что вы пытаетесь решить неполадку &, настоящая проблема в том, что ваше представление данных сломано. Как и проблема круговой рефссы, она также неэффективна в том, что каждый друг дублируется для каждой дружбы. Лучше, чтобы сгладить свой список людей, как это:

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

Храните список в HashMap<Integer, Person> (или SparseArray<Person>). Работа выполнена!

+0

Спасибо! Однако, боюсь, вы неправильно поняли мой вопрос. Я надеялся, что разъясню. Как вы можете видеть, наше предложение API не возвращает полный объект на неопределенный срок. Когда объект уже появился в ответе, возвращается только его идентификатор. Однако то, что я прошу, это предложения для фреймворков, которые способны разбирать такую ​​вещь, правильно устанавливая отношения. – RaffAl

+0

Хорошо, но ваш API по-прежнему остается проблемой. Вы не должны даже думать об этом, просто избегайте циркулярных ссылок в вашей модели. –

+1

Я склонен не согласиться. Избегать циркулярных ссылок в модели по дизайну - это всего лишь один из способов борьбы с ним. Хорошим примером является стандарт JSOG в этом случае, там структура не плоская, круговые ссылки бывают, и описаны почти так же, как я построил свой пример JSON. – RaffAl

1

Вы можете взглянуть на JSON-RPC. Это хорошая структура, которая поддерживает JSON-анализ и отображение объектов сложных отношений объектов.

+0

Спасибо за ответ, проверите это! – RaffAl

Смежные вопросы