{"id":13415060,"url":"https://github.com/ttu/json-flatfile-datastore","last_synced_at":"2025-05-14T11:09:06.516Z","repository":{"id":41459720,"uuid":"85160784","full_name":"ttu/json-flatfile-datastore","owner":"ttu","description":"Simple JSON flat file data store with support for typed and dynamic data.","archived":false,"fork":false,"pushed_at":"2025-02-17T13:04:57.000Z","size":264,"stargazers_count":458,"open_issues_count":13,"forks_count":66,"subscribers_count":26,"default_branch":"master","last_synced_at":"2025-04-13T13:55:10.405Z","etag":null,"topics":["c-sharp","database","datastore","dotnet-core","dynamic","flat-file","json"],"latest_commit_sha":null,"homepage":"https://ttu.github.io/json-flatfile-datastore/","language":"C#","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/ttu.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":"2017-03-16T06:30:34.000Z","updated_at":"2025-03-29T07:12:37.000Z","dependencies_parsed_at":"2024-10-31T17:27:26.728Z","dependency_job_id":"f5e5bc7e-a2e9-4040-9322-6e04c7cbcd45","html_url":"https://github.com/ttu/json-flatfile-datastore","commit_stats":{"total_commits":165,"total_committers":7,"mean_commits":"23.571428571428573","dds":0.06060606060606055,"last_synced_commit":"bfd50b099db0551758b83672bcf44a437a0b642e"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttu%2Fjson-flatfile-datastore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttu%2Fjson-flatfile-datastore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttu%2Fjson-flatfile-datastore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttu%2Fjson-flatfile-datastore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ttu","download_url":"https://codeload.github.com/ttu/json-flatfile-datastore/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254129488,"owners_count":22019628,"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","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":["c-sharp","database","datastore","dotnet-core","dynamic","flat-file","json"],"created_at":"2024-07-30T21:00:42.510Z","updated_at":"2025-05-14T11:09:06.496Z","avatar_url":"https://github.com/ttu.png","language":"C#","readme":"JSON Flat File Data Store\n----------------------------------\n\n[![NuGet](https://img.shields.io/nuget/v/JsonFlatFileDataStore.svg)](https://www.nuget.org/packages/JsonFlatFileDataStore/)\n[![NuGetCount](https://img.shields.io/nuget/dt/JsonFlatFileDataStore.svg\n)](https://www.nuget.org/packages/JsonFlatFileDataStore/)\n\n| Build server| Platform       | Build status                                                                                                                                                                                     |\n|-------------|----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| GH Actions  | Linux          | [![Build Status](https://github.com/ttu/json-flatfile-datastore/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/ttu/json-flatfile-datastore/actions/workflows/ci.yml)      |\n| GH Actions  | Windows        | [![Build Status](https://github.com/ttu/json-flatfile-datastore/actions/workflows/ci_win.yml/badge.svg?branch=master)](https://github.com/ttu/json-flatfile-datastore/actions/workflows/ci_win.yml) |\n\nA lightweight, JSON-based data storage solution, ideal for small applications and prototypes requiring simple, file-based storage.\n\n* A compact API offering essential data-handling capabilities\n* Support for both dynamic and typed data structures\n* Synchronous and asynchronous operations\n* JSON file-based storage with:\n  * Easy initialization\n  * Simple editing\n  * Perfect for small apps and prototyping\n  * Optional encryption for secure data storage\n* .NET implementation \u0026 version support: [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0#select-net-standard-version)\n  * For example, .NET 6, .NET Core 2.0, .NET Framework 4.6.1\n\n**Docs Website**\n\n[https://ttu.github.io/json-flatfile-datastore/](https://ttu.github.io/json-flatfile-datastore/)\n\n---\n\n## Installation\n\nInstall the latest version from [NuGet](https://www.nuget.org/packages/JsonFlatFileDataStore/).\n\n```sh\n# .NET Core CLI\n$ dotnet add package JsonFlatFileDataStore\n\n# Package Manager Console\nPM\u003e Install-Package JsonFlatFileDataStore\n```\n\n## Example\n\n### Typed Data\n\n```csharp\n// Define a simple model class\npublic class Employee\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n    public int Age { get; set; }\n}\n\n// Initialize the data store with a JSON file path (creates a new file if one doesn’t exist)\nvar store = new DataStore(\"data.json\");\n\n// Get a strongly-typed collection\nvar collection = store.GetCollection\u003cEmployee\u003e();\n\n// Create a new employee instance\nvar employee = new Employee { Id = 1, Name = \"John\", Age = 46 };\n\n// Insert a new employee\n// The id is automatically updated to correct next value\nawait collection.InsertOneAsync(employee);\n\n// Update employee information\nemployee.Name = \"John Doe\";\nawait collection.UpdateOneAsync(employee.Id, employee);\n\n// Query using LINQ\nvar results = collection.AsQueryable().Where(e =\u003e e.Age \u003e 30);\n\n// Save instance as a single item\nawait store.InsertItemAsync(\"selected_employee\", employee);\n\n// Single items can be of any type\nawait store.InsertItemAsync(\"counter\", 1);\nvar counter = await store.GetItem\u003cint\u003e(\"counter\");\n```\n\n### Dynamically Typed Data\n\nDynamic data can be any of the following types:\n* `Anonymous type`\n* `ExpandoObject`\n* JSON objects (`JToken`, `JObject`, `JArray`)\n* `Dictionary\u003cstring, object\u003e`\n\nNote: All dynamic data is internally serialized to `ExpandoObject`.\n\n```csharp\n// Open the database (create new if file doesn't exist)\nvar store = new DataStore(pathToJson);\n\n// Get employee collection\nvar collection = store.GetCollection(\"employee\");\n\n// Create new employee\nvar employee = new { id = 1, name = \"John\", age = 46 };\n\n// Create new employee from JSON\nvar employeeJson = JToken.Parse(\"{ 'id': 2, 'name': 'Raymond', 'age': 32 }\");\n\n// Create new employee from dictionary\nvar employeeDict = new Dictionary\u003cstring, object\u003e\n{\n    [\"id\"] = 3,\n    [\"name\"] = \"Andy\",\n    [\"age\"] = 32\n};\n\n// Insert new employee\n// Id is updated automatically if object is updatable\nawait collection.InsertOneAsync(employee);\nawait collection.InsertOneAsync(employeeJson);\nawait collection.InsertOneAsync(employeeDict);\n\n// Update data from anonymous type\nvar updateData = new { name = \"John Doe\" };\n\n// Update data from JSON\nvar updateJson = JToken.Parse(\"{ 'name': 'Raymond Doe' }\");\n\n// Update data from dictionary\nvar updateDict = new Dictionary\u003cstring, object\u003e { [\"name\"] = \"Andy Doe\" };\n\nawait collection.UpdateOneAsync(e =\u003e e.id == 1, updateData);\nawait collection.UpdateOneAsync(e =\u003e e.id == 2, updateJson);\nawait collection.UpdateOneAsync(e =\u003e e.id == 3, updateDict);\n\n// Use LINQ to query items\nvar results = collection.AsQueryable().Where(x =\u003e x.age \u003e 30);\n```\n\n### Example Project\n\n[Fake JSON Server](https://github.com/ttu/dotnet-fake-json-server) is an ASP.NET Web App that uses JSON Flat File Data Store with dynamic data.\n\n## Functionality\n\n### Collections\n\nExample user collection in JSON\n\n```json\n{\n  \"user\": [\n    { \"id\": 1, \"name\": \"Phil\", \"age\": 40, \"city\": \"NY\" },\n    { \"id\": 2, \"name\": \"Larry\", \"age\": 37, \"city\": \"London\" }\n  ]\n}\n```\n\n#### Query\n\nCollections can be queried using LINQ by obtaining a queryable object from the collection with the `AsQueryable` method.\n\nNOTE: While `AsQueryable` returns `IEnumerable` instead of `IQueryable`, this doesn't impact performance since all data is already loaded into memory. The reason for returning `IEnumerable` is that `IQueryable` doesn't support dynamic types in LINQ queries.\n\n`AsQueryable` LINQ query with dynamic data:\n\n```csharp\n// Initialize the data store with a JSON file path (creates a new file if one doesn’t exist)\nvar store = new DataStore(pathToJson);\n\nvar collection = store.GetCollection(\"user\");\n\n// Que item with name\nvar userDynamic = collection\n                    .AsQueryable()\n                    .FirstOrDefault(p =\u003e p.name == \"Phil\");\n```\n\n`AsQueryable` LINQ query with typed data:\n\n```csharp\nvar store = new DataStore(pathToJson);\n\nvar collection = store.GetCollection\u003cUser\u003e();\n\n// Find item with name\nvar userTyped = collection\n                    .AsQueryable()\n                    .FirstOrDefault(p =\u003e p.Name == \"Phil\");\n```\n\n#### Full-Text Search\n\nFull-text search can be performed with the `Find` method. Full-text search performs a deep search on all child objects. __By default__, the search is not case-sensitive.\n\n```csharp\nvar store = new DataStore(pathToJson);\n\nvar collection = store.GetCollection(\"user\");\n\n// Find all users that contain text Alabama in any of property values\nvar matches = collection.Find(\"Alabama\");\n\n// Perform case sensitive search\nvar caseSensitiveMatches = collection.Find(\"Alabama\", true);\n\n```\n\n#### Insert\n\n`InsertOne` and `InsertOneAsync` will insert a new item into the collection. The method returns true if the insert was successful.\n\n```csharp\n// Asynchronous method and dynamic data\n// Before update : { }\n// After update  : { \"id\": 3, \"name\": \"Raymond\", \"age\": 32, \"city\" = \"NY\" }\nawait collection.InsertOneAsync(new { id = 3, name = \"Raymond\", age = 32, city = \"NY\" });\n\n// Dynamic item can also be JSON object\nvar user = JToken.Parse(\"{ 'id': 3, 'name': 'Raymond', 'age': 32, 'city': 'NY' }\");\nawait collection.InsertOneAsync(user);\n\n// Synchronous method and typed data\n// Before update : { }\n// After update  : { \"id\": 3, \"name\": \"Raymond\", \"age\": 32, \"city\" = \"NY\" }\ncollection.InsertOne(new User { Id = 3, Name = \"Raymond\", Age = 32, City = \"NY\" });\n```\n\n`InsertMany` and `InsertManyAsync` will insert a list of items to the collection.\n```csharp\nvar newItems = new[]\n{\n    new User { Id = 3, Name = \"Raymond\", Age = 32, City = \"NY\" },\n    new User { Id = 4, Name = \"Ted\", Age = 43, City = \"NY\" }\n};\n\ncollection.InsertMany(newItems);\n```\n\n`Insert`-methods will update an object's `Id`-field if the field exists and is writable. If the dynamic object is missing an `Id`-field, one will be added with the correct value. When using an `anonymous type` for insertion, if the `id` field is missing, it will be added to the persisted object. If an `id` field is already present, its value will be used.\n\n```csharp\nvar newItems = new[]\n{\n    new { id = 14, name = \"Raymond\", age = 32, city = \"NY\" },\n    new { id = 68, name = \"Ted\", age = 43, city = \"NY\" },\n    new { name = \"Bud\", age = 43, city = \"NY\" }\n};\n\n// Last user will have id 69\ncollection.InsertMany(newItems);\n// Item in newItems collection won't have id property as anonymous types are read only\n```\n\nIf the type of the `id`-field is a *number*, the value is incremented by one. If the type is a *string*, an incremented number is added to the end of the initial text.\n\n```csharp\n// Latest id in the collection is \"hello5\"\nvar user = JToken.Parse(\"{ 'id': 'wrongValue', 'name': 'Raymond', 'age': 32, 'city': 'NY' }\");\nawait collection.InsertOneAsync(user);\n// After addition: user[\"id\"] == \"hello6\"\n\n// User data doesn't have an id field\nvar userNoId = JToken.Parse(\"{ 'name': 'Raymond', 'age': 32, 'city': 'NY' }\");\nawait collection.InsertOneAsync(userNoId);\n// After addition: userNoId[\"id\"] == \"hello7\"\n```\n\nFor an empty collection, if the `id`-field's type is a number, the first id will be `0`. If the type is a string, the first id will be `\"0\"`.\n\n#### Replace\n\n`ReplaceOne` and `ReplaceOneAsync` will replace the first item that matches the filter or the provided id-value that matches the defined id-field. The method returns true if item(s) are found that match the filter.\n\n```csharp\n// Sync and dynamic\n// Before update : { \"id\": 3, \"name\": \"Raymond\", \"age\": 32, \"city\": \"NY\" }\n// After update  : { \"id\": 3, \"name\": \"Barry\", \"age\": 42 }\ncollection.ReplaceOne(3, new { id = 3, name = \"Barry\", age = 33 });\n// or with predicate\ncollection.ReplaceOne(e =\u003e e.id == 3, new { id = 3, name = \"Barry\", age = 33 });\n\n// Async and typed\n// Before update : { \"id\": 3, \"name\": \"Raymond\", \"age\": 32, \"city\": \"NY\" }\n// After update  : { \"id\": 3, \"name\": \"Barry\", \"age\": 42 }\nawait collection.ReplaceOneAsync(3, new User { Id = 3, Name = \"Barry\", Age = 33 });\n```\n\n`ReplaceMany` and `ReplaceManyAsync` will replace all items that match the filter.\n\n```csharp\ncollection.ReplaceMany(e =\u003e e.City == \"NY\", new { City = \"New York\" });\n```\n\n`ReplaceOne` and `ReplaceOneAsync` have an upsert option. If the item to replace doesn't exists in the data store, a new item will be inserted. Upsert won't update the id, so the new item will be inserted with the id that it has.\n\n```csharp\n// New item will be inserted with id 11\ncollection.ReplaceOne(11, new { id = 11, name = \"Theodor\" }, true);\n```\n\n#### Update\n\n`UpdateOne` and `UpdateOneAsync` will update the first item that matches the filter or the provided id-value that matches the defined id-field. Properties to update are defined with a dynamic object. The dynamic object can be an `Anonymous type` or an `ExpandoObject`. The method will return true if item(s) are found that match the filter.\n\n```csharp\n// Dynamic\n// Before update : { \"id\": 1, \"name\": \"Barry\", \"age\": 33 }\n// After update  : { \"id\": 1, \"name\": \"Barry\", \"age\": 42 }\ndynamic source = new ExpandoObject();\nsource.age = 42;\nawait collection.UpdateOneAsync(1, source as object);\n// or with predicate\nawait collection.UpdateOneAsync(e =\u003e e.id == 1, source as object);\n\n// Typed\n// Before update : { \"id\": 1, \"name\": \"Phil\", \"age\": 40, \"city\": \"NY\" }\n// After update  : { \"id\": 1, \"name\": \"Phil\", \"age\": 42, \"city\": \"NY\" }\nawait collection.UpdateOneAsync(e =\u003e e.Name == \"Phil\", new { age = 42 });\n```\n\n`UpdateMany` and `UpdateManyAsync` will update all items that match the filter.\n\n```csharp\nawait collection.UpdateManyAsync(e =\u003e e.Age == 30, new { age = 31 });\n```\n\nUpdate can also update items in the collection and add new items to the collection. `null` items in the passed update data are skipped, so with `null` items, data in the correct index can be updated.\n\n```csharp\nvar family = new Family\n{\n    Id = 12,\n    FamilyName = \"Andersen\",\n    Parents = new List\u003cParent\u003e\n    {\n        new Parent {  FirstName = \"Jim\", Age = 52 }\n    },\n    Address = new Address { City = \"Helsinki\" }\n};\n\nawait collection.InsertOneAsync(family);\n\n// Adds a second parent to the list\nawait collection.UpdateOneAsync(e =\u003e e.Id == 12, new { Parents = new[] { null, new { FirstName = \"Sally\", age = 41 } } });\n\n// Updates the first parent's age to 42\nawait collection.UpdateOneAsync(e =\u003e e.Id == 12, new { Parents = new[] { new { age = 42 } } });\n```\n\nAn easy way to create a patch `ExpandoObject` at runtime is to create a `Dictionary` and then serialize it to a JSON and deserialize it to an `ExpandoObject`.\n\n```csharp\nvar user = new User\n{\n    Id = 12,\n    Name = \"Timmy\",\n    Age = 30,\n    Work = new WorkPlace { Name = \"EMACS\" }\n};\n\n// JSON: { \"Age\": 41, \"Name\": \"James\", \"Work\": { \"Name\": \"ACME\" } }\n// Anonymous type: new { Age = 41, Name = \"James\", Work = new { Name = \"ACME\" } };\nvar patchData = new Dictionary\u003cstring, object\u003e();\npatchData.Add(\"Age\", 41);\npatchData.Add(\"Name\", \"James\");\npatchData.Add(\"Work\", new Dictionary\u003cstring, object\u003e { { \"Name\", \"ACME\" } });\n\nvar jobject = JObject.FromObject(patchData);\ndynamic patchExpando = JsonConvert.DeserializeObject\u003cExpandoObject\u003e(jobject.ToString());\n\nawait collection.UpdateOneAsync(e =\u003e e.Id == 12, patchExpando);\n```\n\n##### Limitations\n\nDictionaries do not work when serializing JSON or data to `ExpandoObjects`. This is becauses dictionaries and objects are similar when serialized to JSON, so serialization creates an `ExpandoObject` from `Dictionary`. Update's are primarily intended for use with `HTTP PATCH`; in most cases, `Replace` provides an easier and more effective way to update data.\n\nIf the update `ExpandoObject` is created manually, then the dictionary's content can be updated. Unlike a `List`, the dictionary's entire content is replaced with the update data's content.\n\n```csharp\nvar player = new Player\n{\n    Id = 423,\n    Scores = new Dictionary\u003cstring, int\u003e\n    {\n        { \"Blue Max\", 1256 },\n        { \"Pacman\", 3221 }\n    },\n};\n\nvar patchData = new ExpandoObject();\nvar items = patchData as IDictionary\u003cstring, object\u003e;\nitems.Add(\"Scores\", new Dictionary\u003cstring, string\u003e { { \"Blue Max\", 1345 }, { \"Outrun\", 1234 }, { \"Pacman\", 3221 }, });\n\nawait collection.UpdateOneAsync(e =\u003e e.Id == 423, patchData);\n```\n\n#### Delete\n\n`DeleteOne` and `DeleteOneAsync` will remove the first object that matches the filter or where the provided id-value matches the defined id-field. Method returns true if an item id found and deleted with the filter or id.\n\n```csharp\n// Dynamic\nawait collection.DeleteOneAsync(3);\nawait collection.DeleteOneAsync(e =\u003e e.id == 3);\n\n// Typed\nawait collection.DeleteOneAsync(3);\nawait collection.DeleteOneAsync(e =\u003e e.Id == 3);\n```\n\n`DeleteMany` and `DeleteManyAsync` will delete all items that match the filter. The method returns true if item(s) are found with the filter.\n\n```csharp\n// Dynamic\nawait collection.DeleteManyAsync(e =\u003e e.city == \"NY\");\n\n// Typed\nawait collection.DeleteManyAsync(e =\u003e e.City == \"NY\");\n```\n\n#### Get Next Id-Field Value\n\nIf incrementing Id-field values are used, `GetNextIdValue` returns the next Id-field value. For integer Id-properties, the last item's value is incremented by one. For non-integer fields, the value is converted to a string and number at the end of the sting is parsed and incremented by one.\n\n```csharp\nvar store = new DataStore(newFilePath, keyProperty: \"myId\");\n\n// myId is an integer\ncollection.InsertOne(new { myId = 2 });\n// nextId = 3\nvar nextId = collection.GetNextIdValue();\n\n// myId is a string\ncollection.InsertOne(new { myId = \"hello\" });\n// nextId = \"hello0\"\nvar nextId = collection.GetNextIdValue();\n\ncollection.InsertOne(new { myId = \"hello3\" });\n// nextId = \"hello4\"\nvar nextId = collection.GetNextIdValue();\n```\n\n### Single Item\n\n```json\n{\n  \"selected_user\": { \"id\": 1, \"name\": \"Phil\", \"age\": 40, \"city\": \"NY\" },\n  \"temperature\": 23.45,\n  \"temperatues\": [ 12.4, 12.42, 12.38 ],\n  \"note\": \"this is a test\"\n}\n```\n\nData store supports single items, which can be either value or reference types. Single items supports both dynamic and typed data.\n\nArrays containing value types are treated as single items. Empty arrays are listed as collections.\n\nSingle items support the same methods as Collections (`Get`, `Insert`, `Replace`, `Update`, `Delete`).\n\n#### Get\n\n```csharp\nvar store = new DataStore(pathToJson);\n// Typed data\nvar counter = store.GetItem\u003cint\u003e(\"counter\");\n// Dynamic data\nvar user = store.GetItem(\"myUser\");\n```\n\nFor typed data, a `KeyNotFoundException` is thrown if the key is not found. For dynamic data and nullable types, null is returned instead.\n\n```csharp\n// throw KeyNotFoundException\nvar counter = store.GetItem\u003cint\u003e(\"counter_NotFound\");\nvar user = store.GetItem\u003cUser\u003e(\"user_NotFound\");\n// return null\nvar counter = store.GetItem\u003cint?\u003e(\"counter_NotFound\");\nvar counter = store.GetItem(\"counter_NotFound\");\n```\n\n#### Insert\n\n`InsertItem` and `InsertItemAsync` will insert a new item into the JSON. These methods return true if the insertion is successful.\n\n```csharp\n// Value type\nvar result = await store.InsertItemAsync(\"counter\", 2);\n// Reference type\nvar user = new User { Id = 12, Name = \"Teddy\" }\nvar result = await store.InsertItemAsync\u003cUser\u003e(\"myUser\", user);\n```\n\n#### Replace\n\n`ReplaceItem` and `ReplaceItemAsync` will replace the item with the key. The method will return true if the item is found with the key.\n\n```csharp\n// Value type\nvar result = await store.ReplaceItemAsync(\"counter\", 4);\n// Reference type\nvar result = await store.ReplaceItemAsync(\"myUser\", new User { Id = 2, Name = \"James\" });\n```\n\n`ReplaceSingleItem` and `ReplaceSingleItem` have an upsert option. If the item to replace doesn't exists in the data store, a new item will be inserted.\n\n```csharp\n// Value type\nvar result = await store.ReplaceItemAsync(\"counter\", 4, true);\n// Reference type\nvar result = await store.ReplaceItemAsync(\"myUser\", new User { Id = 2, Name = \"James\" }, true);\n```\n\n#### Update\n\n`UpdateItem` and `UpdateItemAsync` will update the first item that matches the filter with the passed properties from a dynamic object. The dynamic object can be an `Anonymous type` or an `ExpandoObject`. The method will return true if the item is found with the key.\n\n```csharp\n// Value type\nvar result = await store.UpdateItemAsync(\"counter\", 2);\n// Reference type\nvar result = await store.UpdateItemAsync(\"myUser\", new { name = \"Harold\" });\n```\n\n#### Delete\n\n`DeleteItem` and `DeleteItemAsync` will remove the item that matches the key. The method returns true if the item is found and deleted with the key.\n\n```csharp\n// Sync\nvar result = store.DeleteItem(\"counter\");\n// Async\nvar result = await store.DeleteItemAsync(\"counter\");\n```\n\n## Encrypt JSON-File Content\n\nIt is possible to encrypt the written JSON-data. Passing the `encryptionKey` parameter to the constructor encrypts data using `AES-256`.\n\n```c#\nvar secretKey = \"Key used for encryption\";\nvar store = new DataStore(newFilePath, encryptionKey: secretKey);\n```\n\n## Data Store and Collection Lifecycle\n\nWhen the data store is created, it reads the JSON file into memory. The data store starts a new background thread that handles file access.\n\nWhen the collection is created, it has a lazy reference to the data and will deserialize the JSON to objects when accessed for the first time.\n\nAll write operations in collections are executed immediately internally in the collection, and then the same operation is queued on DataStore's `BlockingCollection`. Operations from the `BlockingCollection` are executed on a background thread to DataStore's internal collection and saved to the file.\n\n```csharp\n// Data is loaded from the file\nvar store = new DataStore(newFilePath);\n\n// Lazy reference to the data is created\nvar collection1st = store.GetCollection(\"hello\");\nvar collection2nd = store.GetCollection(\"hello\");\n\n// Data is loaded from the store to the collection and new item is inserted\ncollection1st.InsertOne(new { id = \"hello\" });\n\n// Data is loaded from the store to the collection and new item is inserted\n// This collection will also have item with id: hello as data is serialized when it is used for the first time\ncollection2nd.InsertOne(new { id = \"hello2\" });\n\n// collection1st won't have item with id hello2\n```\n\nIf multiple DataStores are initialized and used simultaneously, each DataStore will have its own internal state. They might become out of sync with the state in the JSON file, as data is only loaded from the file when the DataStore is initialized and after each commit.\n\nIt is also possible to reload JSON data manually, by using DataStore's `Reload` method or by setting the `reloadBeforeGetCollection` constructor parameter to `true`.\n\n```csharp\n// Data is loaded from the file\nvar store = new DataStore(newFilePath);\nvar store2 = new DataStore(newFilePath, reloadBeforeGetCollection: true);\n\nvar collection1_1 = store.GetCollection(\"hello\");\ncollection1_1.InsertOne(new { id = \"hello\" });\n\n// Because of reload collection2_1 will also have item with id: hello\nvar collection2_1 = store2.GetCollection(\"hello\");\n\ncollection2_1.InsertOne(new { id = \"hello2\" });\n\nstore.Reload()\n\n// Because of reload collection1_2 will also have item with id: hello2\nvar collection1_2 = store.GetCollection(\"hello\");\n\n// collection1_1 will not have item with id: hello2 even after reload, because it was initialized before reload\n```\n\nIf JSON Flat File Data Store is used with, for example, `ASP.NET`, add the `DataStore` to the DI container as a singleton. This way, DataStore's internal state is correct, and the application does not have to rely on the state in the file, as read operation is relatively slow. Reload can be triggered if needed.\n\n## Disposing Data Store\n\nData store should be disposed after it is not needed anymore. Dispose will wait that all writes to the file are completed and after that it will stop the background thread. The garbage collector can then clean up the data store once it is no longer in use.\n\n```csharp\n// Call dispose method\nvar store = new DataStore();\n// ...\nstore.Dispose();\n\n// Call dispose automatically with using\nusing(var store = new DataStore())\n{\n    // ...\n}\n```\n\n## Collection Naming\n\nThe collection name must always be defined when using dynamic collections. Collection names are converted to the selected case.\n\nIf the collection name is not defined with a typed collection, the class name is converted to the selected case. For example. with lower camel case, `User` becomes `user`, and `UserFamily` becomes `userFamily`, etc.\n\n```csharp\nvar store = new DataStore(newFilePath);\n// JSON { \"movie\": [] };\nvar collection = store.GetCollection(\"movie\");\n// JSON { \"movie\": [] };\nvar collection = store.GetCollection(\"Movie\");\n// JSON { \"movie\": [] };\nvar collection = store.GetCollection\u003cMovie\u003e();\n// JSON { \"movies\": [] };\nvar collection = store.GetCollection\u003cMovie\u003e(\"movies\");\n```\n\n## Writing Data to a File\n\nBy default, JSON is written in lower camel case. This can be changed with `useLowerCamelCase` parameter in DataStore's constructor.\n\n```csharp\n// This will write JSON in lower camel case\n// e.g. { \"myMovies\" : [ { \"longName\": \"xxxxx\" } ] }\nvar store = new DataStore(newFilePath);\n\n// This will write JSON in upper camel case\n// e.g. { \"MyMovies\" : [ { \"LongName\": \"xxxxx\" } ] }\nvar store = new DataStore(newFilePath, false);\n```\n\nAdditionally, the file output can be minified. The default is an intended output.\n\n```csharp\nvar store = new DataStore(newFilePath, minifyJson: true);\n```\n\n## Dynamic Types and Error CS1977\n\nWhen __Dynamic type__ is used with lambdas, the compiler will give you error __CS1977__:\n\n\u003e CS1977: Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type\n\nA lambda needs to know the data type of the parameter at compile time. Cast the dynamic type to an object, and the compiler will happily accept it, as it believes you know what you are doing and leaves validation to the Dynamic Language Runtime.\n\n```csharp\ndynamic dynamicUser = new { id = 11, name = \"Theodor\" };\n\n// This will give CS1977 error\ncollection2.ReplaceOne(e =\u003e e.id == 11, dynamicUser);\n\n// Compiler will accept this\ncollection2.ReplaceOne(e =\u003e e.id == 11, dynamicUser as object);\n\n// Compiler will also accept this\ncollection2.ReplaceOne((Predicate\u003cdynamic\u003e)(e =\u003e e.id == 11), dynamicUser);\n```\n\n## Unit Tests \u0026 Benchmarks\n\n`JsonFlatFileDataStore.Test` and `JsonFlatFileDataStore.Benchmark` are _.NET 6_ projects.\n\nUnit tests are executed automatically with CI builds.\n\nBenchmarks are not part of CI builds. Benchmarks can be used as a reference when making changes to the existing functionality by comparing the execution times before and after the changes.\n\nRun benchmarks from the command line:\n```sh\n$ dotnet run --configuration Release --project JsonFlatFileDataStore.Benchmark\\JsonFlatFileDataStore.Benchmark.csproj\n```\n\n## API\n\nAPI is heavily influenced by MongoDB's C# API, so switching to the MongoDB or [DocumentDB](https://docs.microsoft.com/en-us/azure/documentdb/documentdb-protocol-mongodb) might be easy.\n* [MongoDB-C#-linq](http://mongodb.github.io/mongo-csharp-driver/2.4/reference/driver/crud/linq/#queryable)\n* [MongoDB-C#-crud](http://mongodb.github.io/mongo-csharp-driver/2.4/reference/driver/crud/writing/)\n\n## Changelog\n\n[Changelog](CHANGELOG.md)\n\n## Contributing\n\nPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.\n\n## License\n\nLicensed under the [MIT](LICENSE) License.\n","funding_links":[],"categories":["Frameworks, Libraries and Tools","Database","数据库","框架, 库和工具"],"sub_categories":["Database","数据库"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fttu%2Fjson-flatfile-datastore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fttu%2Fjson-flatfile-datastore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fttu%2Fjson-flatfile-datastore/lists"}