{"id":30841850,"url":"https://github.com/dartoos-dev/json_cache","last_synced_at":"2025-09-06T20:10:10.058Z","repository":{"id":42499164,"uuid":"370120597","full_name":"dartoos-dev/json_cache","owner":"dartoos-dev","description":"An object-oriented Flutter package for caching user data locally in json.","archived":false,"fork":false,"pushed_at":"2024-08-30T14:27:24.000Z","size":203,"stargazers_count":16,"open_issues_count":5,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-11-16T03:27:35.596Z","etag":null,"topics":["cache","caching","caching-library","flutter","flutter-apps","flutter-cache","flutter-package","json-cache","preferences"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dartoos-dev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-23T17:52:06.000Z","updated_at":"2024-11-06T02:45:11.000Z","dependencies_parsed_at":"2024-08-29T00:45:25.500Z","dependency_job_id":"85967331-ae32-45c6-aafb-8720ceb53a30","html_url":"https://github.com/dartoos-dev/json_cache","commit_stats":null,"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/dartoos-dev/json_cache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dartoos-dev%2Fjson_cache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dartoos-dev%2Fjson_cache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dartoos-dev%2Fjson_cache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dartoos-dev%2Fjson_cache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dartoos-dev","download_url":"https://codeload.github.com/dartoos-dev/json_cache/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dartoos-dev%2Fjson_cache/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273956236,"owners_count":25197587,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-06T02:00:13.247Z","response_time":2576,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","caching","caching-library","flutter","flutter-apps","flutter-cache","flutter-package","json-cache","preferences"],"created_at":"2025-09-06T20:10:08.109Z","updated_at":"2025-09-06T20:10:10.048Z","avatar_url":"https://github.com/dartoos-dev.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# json_cache\n\n\u003cp align=\"center\"\u003e\n\u003cimg width=\"156\" alt=\"json_cache_img\" src=\"https://user-images.githubusercontent.com/24878574/161399703-c99ef48d-e193-4e4e-a557-bc511d2dfe5e.png\"\u003e\n\u003c/p\u003e\n\n[![EO principles respected\nhere](https://www.elegantobjects.org/badge.svg)](https://www.elegantobjects.org)\n[![DevOps By\nRultor.com](https://www.rultor.com/b/dartoos-dev/json_cache)](https://www.rultor.com/p/dartoos-dev/json_cache)\n    \n[![pub](https://img.shields.io/pub/v/json_cache)](https://pub.dev/packages/json_cache)\n[![license](https://img.shields.io/badge/license-mit-green.svg)](https://github.com/dartoos-dev/json_cache/blob/master/LICENSE)\n[![PDD status](https://www.0pdd.com/svg?name=dartoos-dev/json_cache)](https://www.0pdd.com/p?name=dartoos-dev/json_cache)\n\n[![build](https://github.com/dartoos-dev/json_cache/actions/workflows/build.yml/badge.svg)](https://github.com/dartoos-dev/json_cache/actions/)\n[![codecov](https://codecov.io/gh/dartoos-dev/json_cache/branch/master/graph/badge.svg?token=W6spF0S796)](https://codecov.io/gh/dartoos-dev/json_cache)\n[![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/rafamizes/json_cache)](https://www.codefactor.io/repository/github/rafamizes/json_cache)\n[![style: lint](https://img.shields.io/badge/style-lint-4BC0F5.svg)](https://pub.dev/packages/lint)\n[![Hits-of-Code](https://hitsofcode.com/github/dartoos-dev/json_cache?branch=master)](https://hitsofcode.com/github/dartoos-dev/json_cache/view?branch=master)\n\n## Contents\n\n- [Overview](#overview)\n- [Getting Started](#getting-started)\n  - [Storing Simple Values](#storing-simple-values)\n  - [Suggested Dependency Relationship](#suggested-dependency-relationship)\n- [Implementations](#implementations)\n  - [JsonCacheMem — Thread-safe In-memory cache](#jsoncachemem)\n  - [JsonCacheTry — Enhanced Diagnostic Messages](#jsoncachetry)\n  - [JsonCacheSharedPreferences — SharedPreferences](#jsoncachesharedpreferences)\n  - [JsonCacheLocalStorage — LocalStorage](#jsoncachelocalstorage)\n  - [JsonCacheSafeLocalStorage — SafeLocalStorage](#jsoncachesafelocalstorage)\n  - [JsonCacheFlutterSecureStorage — FlutterSecureStorage](#jsoncachefluttersecurestorage)\n  - [JsonCacheHive — Hive](#jsoncachehive)\n- [Unit Test Tips](#unit-test-tips)\n  - [Mocking](#mocking)\n  - [Fake Implementations](#fake-implementations)\n  - [Widget Testing](#widget-testing)\n    - [Example of Widget Test Code](#example-of-widget-test-code)\n  - [SharedPreferences in Tests](#sharedpreferences-in-tests)\n- [Demo application](#demo-application)\n- [Contribute](#contribute)\n- [References](#references)\n\n## Overview\n\n\u003e Cache is a hardware or software component that stores data so that future\n\u003e requests for that data can be served faster; the data stored in a cache might\n\u003e be the result of an earlier computation or a copy of data stored elsewhere.\n\u003e\n\u003e — [Cache_(computing) (2021, August 22). In Wikipedia, The Free Encyclopedia.\n\u003e Retrieved 09:55, August 22,\n\u003e 2021](https://en.wikipedia.org/wiki/Cache_(computing))\n\n**JsonCache** is an object-oriented package for local caching of user data\nin json. It can also be considered as a layer on top of Flutter's local storage\npackages that aims to unify them with a stable and elegant interface —\n_[JsonCache](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCache-class.html)_.\n\n**Why Json?**\n\n- Because most of the local storage packages available for Flutter applications\n  use Json as the data format.\n- There is a one-to-one relationship between Dart's built-in type `Map\u003cString,\n  dynamic\u003e` and Json, which makes encoding/decoding data in Json a trivial task.\n\n## Getting Started\n\nThis package gives developers great flexibility by providing a set of classes\nthat can be selected and grouped in various combinations to meet specific cache\nrequirements.\n\n[JsonCache](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCache-class.html)\nis the core Dart interface of this package and represents the concept of cached\ndata. It is defined as:\n\n```dart\n/// Represents cached data in json format.\nabstract interface class JsonCache {\n  /// Frees up storage space — deletes all keys and values.\n  Future\u003cvoid\u003e clear();\n\n  /// Removes cached data located at [key].\n  Future\u003cvoid\u003e remove(String key);\n\n  /// Retrieves cached data located at [key] or `null` if a cache miss occurs.\n  Future\u003cMap\u003cString, dynamic\u003e?\u003e value(String key);\n\n  /// It either updates data located at [key] with [value] or, if there is no\n  /// data at [key], creates a new cache row at [key] with [value].\n  ///\n  /// **Note**: [value] must be json encodable.\n  Future\u003cvoid\u003e refresh(String key, Map\u003cString, dynamic\u003e value);\n\n  /// Checks for cached data located at [key].\n  ///\n  /// Returns `true` if there is cached data at [key]; `false` otherwise.\n  Future\u003cbool\u003e contains(String key);\n \n  /// The cache keys.\n  ///\n  /// Returns an **unmodifiable** list of all cache keys without duplicates.\n  Future\u003cUnmodifiableListView\u003cString\u003e\u003e keys();\n}\n```\n\nIt is reasonable to consider each cache entry (a key/data pair) as a group of\nrelated data. Thus, it is expected to cache data into groups, where a key\nrepresents the name of a single data group. For example:\n\n```dart\n'profile': {'name': 'John Doe', 'email': 'johndoe@email.com', 'accountType': 'premium'};\n'preferences': {'theme': {'dark': true}, 'notifications': {'enabled': true}}\n```\n\nAbove, the _profile_ key is associated with profile-related data, while\nthe _preferences_ key is associated with the user's preferences.\n\nA typical code for saving the previous _profile_ and _preferences_ data is:\n\n```dart\nfinal JsonCache jsonCache = … retrieve one of the JsonCache implementations.\n…\nawait jsonCache.refresh('profile', {'name': 'John Doe', 'email': 'johndoe@email.com', 'accountType': 'premium'});\nawait jsonCache.refresh('preferences', {'theme': {'dark': true}, 'notifications':{'enabled': true}});\n```\n\n### Storing Simple Values\n\nIn order to store a simple value such as a `string`, `int`, `double`, etc,\ndefine it as a **map key** whose associated value is a boolean placeholder value\nset to `true`. For example:\n\n```dart\n  /// Storing a phrase.\n  jsonCache.refresh('info', {'This is very important information.': true});\n\n  // later on…\n\n  // This variable is a Map containing a single key.\n  final cachedInfo = await jsonCache.value('info');\n  // The key itself is the content of the stored information.\n  final info = cachedInfo?.keys.first;\n  print(info); // 'This is very important information.'\n\n```\n\n### Suggested Dependency Relationship\n\nWhenever a function, method, or class needs to interact with cached user data,\nthis should be done via a reference to the `JsonCache` interface.\n\nSee the code snippet below:\n\n```dart\n/// Stores/retrieves user data from the device's local storage.\nclass JsonCacheRepository implements ILocalRepository {\n  /// Sets the [JsonCache] instance.\n  const JsonCacheRepository(this._cache);\n  // This class depends on an interface rather than any actual implementation\n  final JsonCache _cache;\n\n  /// Retrieves a cached email by [userId] or `null` if not found.\n  @override\n  Future\u003cString?\u003e getUserEmail(String userId) async {\n    final userData = await _cache.value(userId);\n    if (userData != null) {\n      // the email value or null if absent.\n      return userData['email'] as String?; \n    }\n    // There is no data associated with [userId].\n    return null;\n  }\n}\n```\n\nBy depending on an interface rather than an actual implementation, your code\nbecomes [loosely coupled](https://en.wikipedia.org/wiki/Loose_coupling) to this\npackage — which makes unit testing a lot easier.\n\n## Implementations\n\nThe library\n[JsonCache](https://pub.dev/documentation/json_cache/latest/json_cache/json_cache-library.html)\ncontains all classes that implement the\n[JsonCache](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCache-class.html)\ninterface with more in-depth details.\n\nThe following sections are an overview of each implementation.\n\n### JsonCacheMem\n\n[JsonCacheMem](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCacheMem-class.html)\nis a thread-safe in-memory implementation of the `JsonCache` interface.\nMoreover, it encapsulates a secondary cache or \"slower level2 cache\". Typically,\nthis secondary cache instance is responsible for the local cache; that is, it is\nthe `JsonCache` implementation that actually persists the data on the user's\ndevice.\n\n#### Typical Usage\n\nSince `JsonCacheMem` is a\n[Decorator](https://en.wikipedia.org/wiki/Decorator_pattern), you should\nnormally pass another `JsonCache` instance to it whenever you instantiate a\n`JsonCacheMem` object. For example:\n\n```dart\n  …\n  /// Cache initialization\n  final sharedPrefs = await SharedPreferences.getInstance();\n  final JsonCacheMem jsonCache = JsonCacheMem(JsonCacheSharedPreferences(sharedPrefs));\n  …\n  /// Saving profile and preferences data.\n  await jsonCache.refresh('profile', {'name': 'John Doe', 'email': 'johndoe@email.com', 'accountType': 'premium'});\n  await jsonCache.refresh('preferences', {'theme': {'dark': true}, 'notifications':{'enabled': true}});\n  …\n  /// Retrieving preferences data.\n  final Map\u003cString, dynamic\u003e? preferences = await jsonCache.value('preferences');\n  …\n  /// Frees up cached data before the user leaves the application.\n  Future\u003cvoid\u003e signout() async {\n    await jsonCache.clear();\n  }\n  …\n  /// Removes cached data related to a specific user.\n  Future\u003cvoid\u003e signoutId(String userId) async\n    await jsonCache.remove(userId);\n  }\n```\n\n#### Cache Initialization\n\n[JsonCacheMem.init](https://p.dev/documentatijson_cache/latest/json_cache/JsonCacheMem/JsonCacheMem.init.html)\nis the constructor whose purpose is to initialize the cache upon object\ninstantiation. The data passed to the `init` parameter is deeply copied to both\nthe internal in-memory cache and the level2 cache.\n\n```dart\n  …\n  final LocalStorage storage = LocalStorage('my_data');\n  final Map\u003cString, Map\u003cString, dynamic\u003e?\u003e initData = await fetchData();\n  final JsonCacheMem jsonCache = JsonCacheMem.init(initData, level2:JsonCacheLocalStorage(storage));\n  …\n```\n\n### JsonCacheTry\n\n[JsonCacheTry](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCacheTry-class.html)\nis an implementation of the `JsonCache` interface whose sole purpose is to\nsupply enhanced diagnostic information when a cache failure occurs. It does this\nby throwing [JsonCacheException](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCacheException-class.html)\nwith the underlying stack trace.\n\nSince `JsonCacheTry` is a\n[Decorator](https://en.wikipedia.org/wiki/Decorator_pattern), you must pass\nanother `JsonCache` instance to it whenever you instantiate a `JsonCacheTry`\nobject. For example:\n\n```dart\n  …\n  // Local storage cache initialization\n  final sharedPrefs = await SharedPreferences.getInstance();\n  // JsonCacheTry instance initialized with in-memory and local storage caches.\n  final jsonCacheTry = JsonCacheTry(JsonCacheMem(JsonCacheSharedPreferences(sharedPrefs)));\n  …\n```\n\n### JsonCacheSharedPreferences\n\n[JsonCacheSharedPreferences](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCacheSharedPreferences-class.html)\nis an implementation on top of the\n[shared_preferences](https://pub.dev/packages/shared_preferences) package.\n\n```dart\n  …\n  final sharedPrefs = await SharedPreferences.getInstance();\n  final JsonCache jsonCache = JsonCacheMem(JsonCacheSharedPreferences(sharedPrefs));\n  …\n```\n\n### JsonCacheLocalStorage\n\n[JsonCacheLocalStorage](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCacheLocalStorage-class.html)\nis an implementation on top of the\n[localstorage](https://pub.dev/packages/localstorage) package.\n\n```dart\nimport 'package:flutter/material.dart';\nimport 'package:localstorage/localstorage.dart';\n\n  …\n  WidgetsFlutterBinding.ensureInitialized();\n  await initLocalStorage();\n  final JsonCache jsonCache = JsonCacheMem(JsonCacheLocalStorage(localStorage));\n  …\n\n### JsonCacheSafeLocalStorage\n\n[JsonCacheSafeLocalStorage](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCacheSafeLocalStorage-class.html)\nis an implementation on top of the\n[safe_local_storage](https://pub.dev/packages/safe_local_storage) package.\n\n```dart\n  …\n  final storage = SafeLocalStorage('/path/to/your/cache/file.json');\n  final JsonCache jsonCache = JsonCacheMem(JsonCacheSafeLocalStorage(storage));\n  …\n```\n\n### JsonCacheFlutterSecureStorage\n\nJsonCacheFlutterSecureStorage\nis an implementation on top of the\n[flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage) package.\n\n```dart\n  …\n  final flutterSecureStorage = FlutterSecureStorage(…);\n  final JsonCache jsonCache = JsonCacheFlutterSecureStorage(flutterSecureStorage);\n  // In order to write a string value, define it as a map key whose associated\n  // value is a boolean placeholder value set to 'true'.\n  jsonCache.refresh('secret', {'a secret info': true});\n\n  // later on…\n\n  final cachedInfo = await jsonCache.value('secret');\n  final info = cachedInfo?.keys.first; // 'a secret info'\n```\n\n### JsonCacheHive\n\n[JsonCacheHive](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCacheHive.html)\nis an implementation on top of the [hive](https://pub.dev/packages/hive)\npackage.\n\n```dart\n  …\n  await Hive.initFlutter(); // mandatory initialization.\n  final box = await Hive.openBox\u003cString\u003e('appBox'); // it must be a Box\u003cString\u003e.\n  final JsonCache hiveCache = JsonCacheMem(JsonCacheHive(box));\n  …\n```\n\n## Unit Test Tips\n\nThis package has been designed with unit testing in mind. This is one of the\nreasons for the existence of the `JsonCache` interface.\n\n### Mocking\n\nSince `JsonCache` is the core interface of this package, you can easily\n[mock](https://docs.flutter.dev/cookbook/testing/unit/mocking) a implementation\nthat suits you when unit testing your code.\n\nFor example, with [mocktail](https://pub.dev/packages/mocktail) a mock\nimplementation should look like this:\n\n```dart\nimport 'package:mocktail/mocktail.dart';\n\nclass JsonCacheMock extends Mock implements JsonCache {}\n\nvoid main() {\n  // the mock instance.\n  final jsonCacheMock = JsonCacheMock();\n\n  test('should retrieve the preferences data', () async {\n    // Stub the 'value' method.\n    when(() =\u003e jsonCacheMock.value('preferences')).thenAnswer(\n      (_) async =\u003e \u003cString, dynamic\u003e{\n        'theme': {'dark': true},\n        'notifications': {'enabled': true}\n      },\n    );\n\n    // Verify no interactions have occurred.\n    verifyNever(() =\u003e jsonCacheMock.value('preferences'));\n\n    // Interact with the jsonCacheMock instance.\n    final preferencesData = await jsonCacheMock.value('preferences');\n\n    // Assert\n    expect(\n      preferencesData,\n      equals(\n        \u003cString, dynamic\u003e{\n          'theme': {'dark': true},\n          'notifications': {'enabled': true}\n        },\n      ),\n    );\n\n    // Check if the interaction occurred only once.\n    verify(() =\u003e jsonCacheMock.value('preferences')).called(1);\n  });\n}\n\n```\n\n### Fake Implementations\n\nIn addition to mocking, there is another approach to unit testing: making use of\na 'fake' implementation. Usually this so-called 'fake' implementation provides\nthe functionality required by the `JsonCache` interface without touching the\ndevice's local storage. An example of this implementation is the\n[JsonCacheFake](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCacheFake-class.html)\nclass — whose sole purpose is to help developers with unit tests.\n\n### Widget Testing\n\nBecause of the asynchronous nature of dealing with cached data, you're better\noff putting all your test code inside a `tester.runAsync` method; otherwise,\nyour test case may stall due to a\n[deadlock](https://en.wikipedia.org/wiki/Deadlock) caused by a [race\ncondition](https://stackoverflow.com/questions/34510/what-is-a-race-condition)\nas there might be multiple `Futures` trying to access the same resources at the\nsame time.\n\n#### Example of Widget Test Code\n\nYour widget test code should look similar to the following code snippet:\n\n```dart\ntestWidgets('refresh cached value', (WidgetTester tester) async {\n  final LocalStorage localStorage = LocalStorage('my_cached_data');\n  final jsonCache = JsonCacheMem(JsonCacheLocalStorage(localStorage));\n  tester.runAsync(() async {\n    // asynchronous code inside runAsync.\n    await jsonCache.refresh('test', \u003cString, dynamic\u003e{'aKey': 'aValue'});\n  });\n});\n```\n\n### SharedPreferences in Tests\n\nWhenever you run any unit tests involving the\n[shared_preferences](https://pub.dev/packages/shared_preferences) package, you\nmust call the `SharedPreferences.setMockInitialValues()` function at the very\nbeginning of the test file; otherwise, the system may throw an error whose\ndescription is: 'Binding has not yet been initialized'.\n\nExample:\n\n```dart\n\nvoid main() {\n  SharedPreferences.setMockInitialValues({});\n  // the test cases come below\n  … \n}\n```\n\n## Demo application\n\nThe demo application provides a fully working example, focused on demonstrating\nthe caching API in action. You can take the code in this demo and experiment\nwith it.\n\nTo run the demo application:\n\n```shell\n  git clone https://github.com/dartoos-dev/json_cache.git\n  cd json_cache/example/\n  flutter run -d chrome\n```\n\nThis should launch the demo application on Chrome in debug mode.\n\n## Contribute\n\nContributors are welcome!\n\n1. Open an **issue** regarding an improvement, a bug you noticed, or ask to be\n   assigned to an existing one.\n2. If the issue is confirmed, **fork** the repository, do the changes on a\n   separate branch and make a **Pull Request**.\n3. After review and acceptance, the PR is merged and closed.\n\nMake sure the command below **passes** before making a Pull Request.\n\n```shell\n  flutter analyze \u0026\u0026 flutter test\n```\n\n## References\n\n- [Dart and race conditions](https://pub.dev/packages/mutex)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdartoos-dev%2Fjson_cache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdartoos-dev%2Fjson_cache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdartoos-dev%2Fjson_cache/lists"}