2016-01-21 12 views
6

Sto lavorando su un progetto Android e attualmente sto cercando di capire come deserializzare qualche JSON dai nostri API s che include cicli di riferimento in un oggetto grafico, che posso quindi manipolare e archiviare in un database. Vi faccio un esempio:quadro di analisi che si occupa bene con i riferimenti circolari in JSON

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

Qui, un oggetto persona chiamata Bob è diventata amica di persona Alice, e Alice è in amici di giro con Bob. Poiché la relazione è ricorsiva, la relazione degli amici di Alice a Bob non viene più realizzata come oggetto a persona intera, ma viene fornito solo il suo id.

Quali strumenti utilizzate per eseguire i passaggi sopra indicati? Ho provato a implementare la parte di mappatura degli oggetti con Jackson ma non sono riuscito a trovare una soluzione per il requisito del ciclo. Ho trovato un ongoing discussion su questo argomento che menziona JSOG che potrebbe essere utile, ma le nostre API sono fisse e non conformi a JSOG.

Fondamentalmente quello che sto cercando è qualcosa come RestKit (framework iOS) per Android.

+0

Posso creare un metodo per analizzare l'id, il nome, gli amici e restituirlo come elenco o qualsiasi struttura dati per ulteriori processi, sarebbe di aiuto? –

risposta

4

volta API è fisso, mi piacerebbe attuarla in questo modo:

Dal punto di vista DB, avrei 2 tavoli - usertable e RelationsTable per mantenere tutti i bordi dei tuoi amici Grafico:
enter image description here

Ie l'idea è di mantenere gli utenti in una tabella e le loro relazioni nella tabella delle relazioni. Permette anche di aggiungere qualche logica extra su di esso più tardi (ad esempio, l'utente nasconde la sua connessione o blocca qualcuno, ecc. - eventuali spigoli del grafico). Inoltre, consente di mitigare i problemi con i riferimenti circolari.

Come framework per recuperare i dati dal servizio & parse jsons, vorrei usare Retrofit.

In primo luogo, mi definisco UserBase e User classi:

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

dove, come si può vedere, friends è un elenco di oggetti per UserBaseRetrofit per analizzare l'oggetto dal JSON e userFriends - l'elenco, che riempiremo manualmente da SQLite in ulteriori passaggi.

Ora, definiamo alcuni help-classi per operare con 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; 
    } 
} 

Ora abbiamo bisogno di Dao di per RelatedTable e per 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); 
    ... 
} 

Una volta fatto, si può effettivamente attuare questa interfacce:

DefaultUserRelationDao classe:

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

e DefaultUserDao classe:

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

Per creare le nostre tavole, abbiamo bisogno di estendere SQLiteOpenHelper e in onCreate() tabelle effettivamente creare:

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

Ora, io suggerirei di definire l'interfaccia LocalStorage con tutta possibili azioni con cache:

  • ottenere tutti gli utenti
  • utente
  • get da id
  • aggiungere utenti
  • aggiungere il collegamento tra gli utenti
  • ecc

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

e la sua implementazione:

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

Ultimo passo - l'attuale utilizzo di esso:

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

NB! Sto usando molto qui la mia classe personalizzata SqlUtilities. Purtroppo, è troppo grande per postare qui, ma solo un esempio per dare alcune idee che cosa c'è dentro - ecco come getusers (cursore Cursore) sembra non:

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

spero, mi perdonerai saltare un po ' dettagli (in particolare, per quanto riguarda il caso, quando DB deve essere aggiornato, quando i dati non sono completi e oltre a scaricarlo dalla cache, è necessario recuperarlo dal server prima, e quindi caricarlo nella cache, ecc.). Se manca qualche parte cruciale - per favore, postalo nei commenti e sarò lieto di aggiornare il post.

Spero, ti aiuterà.

+1

Grazie per il feedback, le tue idee hanno già aiutato molto! Tuttavia, la tua risposta salta la parte di parsing della mia domanda, come gestire i cicli in JSON (usando framework o codice personalizzato), questo è quello che mi interessa di più. Qualche idea? – RaffAl

+2

@Bearwithme, in realtà, non è così :-) Retrofit analizza il tuo JSON da solo. Basta mantenere le proprietà del tuo 'utente' che vuoi analizzare, come' pubblico' e con gli stessi nomi, come in JSON, e il framework farà il suo lavoro automaticamente –

+2

cioè, come puoi vedere, sto analizzando l'elenco degli amici come lista di oggetti di classe 'UserBase' (id), e non andare più in profondità nella ricorsione –

0

Direi che stai cercando di risolvere il problema sbagliato & il vero problema è che la rappresentazione dei dati è rotta. Così come il problema dei refs circolari è anche inefficiente in quanto ogni amico viene duplicato per ogni amicizia. Meglio per appiattire la lista delle persone piace questo:

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

memorizzare l'elenco in un HashMap<Integer, Person> (o SparseArray<Person>). Lavoro fatto!

+0

Grazie per il tuo feedback! Tuttavia, temo abbiate frainteso la mia domanda. Speravo di averlo chiarito. Come puoi vedere, la nostra proposta API non restituisce l'oggetto completo a tempo indeterminato. Quando l'oggetto è già apparso nella risposta, viene restituito solo l'ID. Tuttavia, quello che sto chiedendo sono suggerimenti per quadri che siano in grado di analizzare ciò, stabilendo correttamente le relazioni. – RaffAl

+0

Ok ma la tua API è ancora il problema. Non dovresti nemmeno pensarci, basta evitare i riferimenti circolari nel modello. –

+1

Tendo a non essere d'accordo. Evitare i riferimenti circolari nel modello di progettazione è solo un modo per affrontarlo. Un buon esempio è lo standard JSOG in questo caso, la struttura non è piatta, i riferimenti circolari si verificano e sono descritti quasi nello stesso modo in cui ho costruito il mio esempio JSON. – RaffAl

1

È possibile dare un'occhiata a JSON-RPC. Questo è un buon framework che supporta l'analisi di JSON e la mappatura di oggetti della relazione di oggetti complessi.

+0

Grazie per la risposta, controlleremo! – RaffAl

Problemi correlati