Title: Backup silently corrupts all servers/keys since #1196 — custom toEncodable disables .toJson(), objects serialized via toString()
Describe the bug
Since PR #1196 (first shipped in v1448), BackupV2.backup() passes a custom toEncodable to json.encode:
// lib/data/model/app/bak/backup2.dart:117
var result = json.encode(bak.toJson(), toEncodable: _toEncodable);
In Dart, providing a custom toEncodable replaces the encoder's default behavior of calling .toJson() on non-primitive objects. The fallback then stringifies everything that is not an Enum:
// lib/data/model/app/bak/backup2.dart:141-145
Object? _toEncodable(Object? value) {
if (value is Enum) return value.name;
_loggerV2.warning('Non-JSON-encodable type: ...');
return value.toString();
}
Entries stored in Hive as typed objects (Spi, PrivateKeyInfo, snippets — via the adapters in lib/hive/hive_registrar.g.dart) end up in the backup as strings like "Spi<user@host:22>" instead of JSON objects, losing all credentials/settings at backup time.
On restore, these strings are dropped silently:
// lib/data/store/cached_store.dart:84-95 (_getAndConvert)
if (raw is Map) {
try {
final item = fromJson(Map<String, dynamic>.from(raw));
...
} catch (e) {
dprint('Parsing $T from JSON', e); // debug-only
}
}
return null; // String entries are skipped without any user-visible error
WebDAV/iCloud sync uses the same BackupV2 path (lib/core/sync.dart) and is equally affected.
To Reproduce
- On v1448+ (tested with v1450), have servers stored (create some, open the server list once)
- Go to Settings → Backup, create a backup (file or WebDAV)
- Restore this backup on another device (or after clearing data)
- See empty server list, keys, and snippets — without any error message
Desired Results
Backup contains all servers/keys/snippets as JSON objects; restore brings them back. If serialization or parsing of an entry fails, the user is informed instead of data being dropped silently.
Actual Results
Every entry under spis/keys in the backup is a String (e.g. "Spi<user@host:22>"), not a JSON object — verified by decrypting a v1450 backup externally per Cryptor's AES-GCM/PBKDF2 format: 68/68 server entries corrupted. Restore completes without any visible error, but everything is empty. A backup created with v1426 (before #1196) contains proper JSON objects and restores fine, even on v1450.
Screenshots
Decrypted v1450 backup (external check):
spis: 68 Eintraege
mbx03741620: PROBLEM -> Typ ist str, kein dict! ("Spi<user@host:...")
mbx06b21ee9: PROBLEM -> Typ ist str, kein dict!
...
Device
- OS: Android 16
- App Version: 1.0.1450 (bug present since 1.0.1448; 1.0.1426 works)
Additional context
Regression introduced by #1196 (18 Jun 2026), which was meant to fix the AppTab enum crash (#1194) but converted a loud failure into silent data corruption for all typed objects.
Suggested fix: in _toEncodable, try (value as dynamic).toJson() before falling back to toString() (or serialize stores explicitly via their models' toJson). Independently, CachedHiveStore._getAndConvert should surface skipped/unparsable entries to the user.
Title: Backup silently corrupts all servers/keys since #1196 — custom
toEncodabledisables.toJson(), objects serialized viatoString()Describe the bug
Since PR #1196 (first shipped in v1448),
BackupV2.backup()passes a customtoEncodabletojson.encode:In Dart, providing a custom
toEncodablereplaces the encoder's default behavior of calling.toJson()on non-primitive objects. The fallback then stringifies everything that is not anEnum:Entries stored in Hive as typed objects (
Spi,PrivateKeyInfo, snippets — via the adapters inlib/hive/hive_registrar.g.dart) end up in the backup as strings like"Spi<user@host:22>"instead of JSON objects, losing all credentials/settings at backup time.On restore, these strings are dropped silently:
WebDAV/iCloud sync uses the same
BackupV2path (lib/core/sync.dart) and is equally affected.To Reproduce
Desired Results
Backup contains all servers/keys/snippets as JSON objects; restore brings them back. If serialization or parsing of an entry fails, the user is informed instead of data being dropped silently.
Actual Results
Every entry under
spis/keysin the backup is aString(e.g."Spi<user@host:22>"), not a JSON object — verified by decrypting a v1450 backup externally perCryptor's AES-GCM/PBKDF2 format: 68/68 server entries corrupted. Restore completes without any visible error, but everything is empty. A backup created with v1426 (before #1196) contains proper JSON objects and restores fine, even on v1450.Screenshots
Decrypted v1450 backup (external check):
Device
Additional context
Regression introduced by #1196 (18 Jun 2026), which was meant to fix the
AppTabenum crash (#1194) but converted a loud failure into silent data corruption for all typed objects.Suggested fix: in
_toEncodable, try(value as dynamic).toJson()before falling back totoString()(or serialize stores explicitly via their models'toJson). Independently,CachedHiveStore._getAndConvertshould surface skipped/unparsable entries to the user.