2016-01-21 5 views
6

ich auf einem Android-Projekt arbeite und ich versuche zur Zeit, um herauszufinden, wie einige JSON unseres API s deserialisieren, die Referenzzyklen in einen Objektgraph enthält, den ich dann manipuliere und speicher in einer Datenbank. Lassen Sie mich ein Beispiel geben:Parsing-Framework, das gut mit zirkulären Referenzen in JSON beschäftigt

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

Hier wird eine Person Objekt namens Bob ist befreundet mit Person Alice und Alice ist wiederum Freunde mit Bob. Da die Beziehung rekursiv ist, ist die Alice 's Freunde-Beziehung zu Bob nicht mehr als vollständiges Personenobjekt realisiert, sondern nur seine id wird bereitgestellt.

Welche Werkzeuge verwenden Sie, um die oben genannten Schritte durchzuführen? Ich habe versucht, den Objekt-Mapping-Teil mit Jackson zu implementieren, konnte aber keine Lösung für die Zyklusanforderung finden. Ich habe eine ongoing discussion zu diesem Thema gefunden, die JSOG erwähnt, die hilfreich sein könnte, aber unsere APIs sind fest und nicht JSOG-kompatibel.

Im Grunde, was ich suche, ist etwas wie RestKit (iOS-Framework) für Android.

+0

Ich kann Ihnen eine Methode erstellen, um die ID, Namen, Freunde analysieren und als Liste oder eine beliebige Datenstruktur für den weiteren Prozess zurückgeben, würde das helfen? –

Antwort

4

Sobald API festgelegt ist, ich es auf diese Weise implementieren würde:

Von DB Perspektive, I 2 Tabellen haben würde - Usertable und RelationsTable alle Kanten Ihrer Freunde zu halten grafisch darstellen:
enter image description here

Ie Die Idee ist, Benutzer in der einen Tabelle und ihre Beziehungen in Relationen Tabelle zu halten. Es erlaubt auch, später eine zusätzliche Logik hinzuzufügen (zum Beispiel blendet der Benutzer seine Verbindung aus oder blockiert jemanden, usw. - irgendwelche möglichen Kanten des Graphen). Auch ermöglicht es, Probleme mit kreisförmigen Referenzen zu verringern.

Als Framework zum Abrufen von Daten aus Service & Parse JSons, würde ich Retrofit verwenden.

Zuerst würde ich UserBase und User Klassen definieren:

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

wo, wie Sie sehen können, friends ist eine Liste von UserBase Objekte für Retrofit das Objekt von JSON und userFriends zu analysieren - die Liste, welche wir in weiteren Schritten manuell aus SQLite füllen werden.

Nun lassen Sie uns einige Hilfe-Klassen definieren mit DBs zu bedienen:

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

Jetzt müssen wir Dao für RELATED und für 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); 
    ... 
} 

Sobald es fertig ist, wir dies tatsächlich umsetzen können Schnittstellen:

DefaultUserRelationDao Klasse:

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

und DefaultUserDao Klasse:

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

unsere Tabellen erstellen, müssen wir SQLiteOpenHelper und in onCreate() tatsächlich Tabellen erstellen erweitern:

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

Jetzt würde ich vorschlagen, Localstorage-Schnittstelle mit allen definieren mögliche Aktionen mit Cache:

  • erhalten Sie alle Benutzer
  • get Benutzer von id
  • Benutzer hinzufügen
  • Verbindung zwischen Benutzer
  • usw.

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

hinzufügen und es ist die Implementierung:

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

Letzter Schritt - die tatsächliche Verwendung davon:

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

Achtung! Ich benutze hier stark meine benutzerdefinierte Klasse SqlUtilities. Leider ist es viel zu groß, um es hier zu posten, aber nur ein Beispiel einige Ideen zu geben, was drin ist - hier ist, wie GetUsers (Cursor Cursor) sieht es:

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

Ich hoffe, werde verzeihen Sie mir einige Skipping Details (vor allem in Bezug auf den Fall, wenn DB aktualisiert werden muss, wenn Daten nicht voll ist und neben dem Abrufen aus dem Cache, müssen Sie es zuerst vom Server abrufen, und laden Sie es dann in den Cache usw.). Wenn ein wichtiger Teil fehlt - bitte, posten Sie es in Kommentaren und ich werde mich freuen, den Beitrag zu aktualisieren.

Ich hoffe, es wird Ihnen helfen.

+1

Danke für das Feedback, Ihre Ideen haben schon viel geholfen! Ihre Antwort überspringt jedoch den Parsing-Teil meiner Frage, wie mit Zyklen in JSON umzugehen ist (mit Framework oder benutzerdefiniertem Code), das interessiert mich am meisten. Irgendwelche Ideen? – RaffAl

+2

@Bearwithme eigentlich, es ist nicht :-) Retrofit analysiert Ihr JSON selbst. Bewahren Sie einfach Eigenschaften Ihres 'Benutzers' auf, den Sie analysieren möchten, als' public' und mit denselben Namen wie in JSON, und das Framework erledigt seine Arbeit automatisch. –

+2

dh wie Sie sehen können, analysiere ich die Liste von Freundesliste von 'UserBase' Klassenobjekten (ids), und gehen Sie nicht tiefer in die Rekursion –

0

Ich würde sagen, Sie versuchen, das falsche Problem zu lösen & das eigentliche Problem ist, dass Ihre Daten Darstellung gebrochen ist. Ebenso wie das zirkuläre Refs-Problem ist es auch ineffizient, da jeder Freund für jede Freundschaft dupliziert wird. Bessere Liste der Menschen so zu glätten:

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

Shop die Liste in einem HashMap<Integer, Person> (oder SparseArray<Person>). Job erledigt!

+0

Vielen Dank für Ihr Feedback! Aber ich fürchte, Sie haben meine Frage falsch verstanden. Ich hoffte, ich machte es klar. Wie Sie sehen können, gibt unser API-Vorschlag das vollständige Objekt nicht unbegrenzt zurück. Wenn das Objekt bereits in der Antwort aufgetreten ist, wird nur seine ID zurückgegeben. Was ich jedoch verlange, sind Vorschläge für Frameworks, die in der Lage sind, solche Dinge zu analysieren und die Beziehungen korrekt herzustellen. – RaffAl

+0

Ok, aber deine API ist immer noch das Problem. Sie sollten nicht einmal darüber nachdenken müssen, vermeiden Sie einfach kreisförmige Refs in Ihrem Modell. –

+1

Ich stimme eher nicht zu. Circular Refs im Modell zu vermeiden, ist nur eine Möglichkeit, damit umzugehen. Ein gutes Beispiel ist der JSOG-Standard in diesem Fall, da die Struktur nicht flach ist, zirkuläre Referenzen passieren und fast genauso beschrieben werden, wie ich mein Beispiel-JSON konstruiert habe. – RaffAl

1

Sie können einen Blick in JSON-RPC werfen. Dies ist ein gutes Framework, das JSON-Parsing und Objekt-Mapping komplexer Objektbeziehungen unterstützt.

+0

Danke für die Antwort, wird dies überprüfen! – RaffAl

Verwandte Themen