{"id":15030135,"url":"https://github.com/3lvis/sync","last_synced_at":"2025-04-09T11:03:15.692Z","repository":{"id":22731575,"uuid":"26076338","full_name":"3lvis/Sync","owner":"3lvis","description":"JSON to Core Data and back. Swift Core Data Sync. ","archived":false,"fork":false,"pushed_at":"2024-06-10T22:55:43.000Z","size":86503,"stargazers_count":2553,"open_issues_count":11,"forks_count":262,"subscribers_count":64,"default_branch":"master","last_synced_at":"2024-10-29T15:34:08.973Z","etag":null,"topics":["carthage","cocoapods","core-data","coredata","json","restkit","swift","sync"],"latest_commit_sha":null,"homepage":"","language":"Swift","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/3lvis.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":"FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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},"funding":{"github":["3lvis"]}},"created_at":"2014-11-02T09:24:29.000Z","updated_at":"2024-10-22T12:40:55.000Z","dependencies_parsed_at":"2024-11-06T22:31:28.062Z","dependency_job_id":"1038ec58-fc45-49ff-aa1b-6f4e2e9adb6f","html_url":"https://github.com/3lvis/Sync","commit_stats":{"total_commits":1514,"total_committers":38,"mean_commits":39.8421052631579,"dds":0.4610303830911493,"last_synced_commit":"3d09f46cfaa46ba4968125a345a897e8f86aa4dd"},"previous_names":["nselvis/kipu","nselvis/nsmanagedobject-andynetworking"],"tags_count":131,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3lvis%2FSync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3lvis%2FSync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3lvis%2FSync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3lvis%2FSync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/3lvis","download_url":"https://codeload.github.com/3lvis/Sync/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248027404,"owners_count":21035594,"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":["carthage","cocoapods","core-data","coredata","json","restkit","swift","sync"],"created_at":"2024-09-24T20:12:32.570Z","updated_at":"2025-04-09T11:03:15.672Z","avatar_url":"https://github.com/3lvis.png","language":"Swift","readme":"**Notice: Sync was supported from it's creation back in 2014 until March 2021**\n\nMoving forward I won't be able to support this project since I'm no longer active in making iOS apps with Core Data. I'm leaving this repository as a historical reference of what happened during this time. Sync had in total 130 releases with almost 30 contributors. If you still support this project I encourage you to fork it and continue the development. I don't feel comfortable with passing this project to another developer due to the fact that I want to have some involvement in all the projects that live under my account. 7 years was a good run, thank you everyone for using this project and thank you to everyone that has contributed to it. Best of luck in your careers and in what this constantly evolving tech world has for all of us. \n\nInitial releases: https://github.com/3lvis/Sync/releases?after=0.4.1\n\n--------------------------------------------------------------------\n\n![Sync](https://raw.githubusercontent.com/3lvis/Sync/master/Images/logo-v3.png)\n\n**Sync** eases your everyday job of parsing a JSON response and syncing it with Core Data. **Sync** is a lightweight Swift library that uses a convention-over-configuration paradigm to facilitate your workflow.\n\n\u003cdiv align = \"center\"\u003e\n  \u003ca href=\"https://cocoapods.org/pods/Sync\"\u003e\n\u003cimg src=\"https://img.shields.io/cocoapods/v/Sync.svg?style=flat\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/3lvis/Sync\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/3lvis/Sync#installation\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/compatible-swift%205.0-orange.svg\" /\u003e\n  \u003c/a\u003e\n\u003c/div\u003e\n\n\u003cdiv align = \"center\"\u003e\n  \u003ca href=\"https://cocoapods.org/pods/Sync\" target=\"blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/cocoapods/p/Sync.svg?style=flat\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://cocoapods.org/pods/Sync\" target=\"blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/cocoapods/l/Sync.svg?style=flat\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://gitter.im/3lvis/Sync\"\u003e\n    \u003cimg src=\"https://img.shields.io/gitter/room/nwjs/nw.js.svg\" /\u003e\n  \u003c/a\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n\u003c/div\u003e\n\nSyncing JSON to Core Data is a repetitive tasks that often demands adding a lot of boilerplate code. Mapping attributes, mapping relationships, diffing for inserts, removals and updates are often tasks that don't change between apps. Taking this in account we took the challenge to abstract this into a library. **Sync** uses the knowledge of your Core Data model to infer all the mapping between your JSON and Core Data, once you use it, it feels so obvious that you'll wonder why you weren't doing this before.\n\n* Automatic mapping of camelCase or snake_case JSON into Core Data\n* Thread-safe saving, we handle retrieving and storing objects in the right threads\n* Diffing of changes, updated, inserted and deleted objects (which are automatically purged for you)\n* Auto-mapping of relationships (one-to-one, one-to-many and many-to-many)\n* Smart-updates, only updates your `NSManagedObject`s if the server values are different from your local ones\n* Uniquing, one Core Data entry per primary key\n* `NSOperation` subclass, any Sync process can be queued and cancelled at any time!\n\n## Table of Contents\n\n* [Basic example](#basic-example)\n* [Demo project](#demo-project)\n* [Getting Started](#getting-started)\n  * [Core Data Stack](#core-data-stack)\n  * [Primary Key](#primary-key)\n  * [Attribute Mapping](#attribute-mapping)\n  * [Attribute Types](#attribute-types)\n  * [Relationship Mapping](#relationship-mapping)\n    * [One-to-many](#one-to-many)\n    * [One-to-many (simplified)](#one-to-many-simplified)\n    * [One-to-one](#one-to-one)\n    * [One-to-one (simplified)](#one-to-one-simplified)\n  * [JSON Exporting](#json-exporting)\n* [FAQ](#faq)\n* [Installation](#installation)\n* [License](#license)\n\n## Basic example\n\n### Model\n\n![Model](https://raw.githubusercontent.com/3lvis/Sync/master/Images/one-to-many-swift.png)\n\n### JSON\n\n```json\n[\n  {\n    \"id\": 6,\n    \"name\": \"Shawn Merrill\",\n    \"email\": \"shawn@ovium.com\",\n    \"created_at\": \"2014-02-14T04:30:10+00:00\",\n    \"updated_at\": \"2014-02-17T10:01:12+00:00\",\n    \"notes\": [\n      {\n        \"id\": 0,\n        \"text\": \"Shawn Merril's diary, episode 1\",\n        \"created_at\": \"2014-03-11T19:11:00+00:00\",\n        \"updated_at\": \"2014-04-18T22:01:00+00:00\"\n      }\n    ]\n  }\n]\n```\n\n### DataStack\n\nDataStack is a wrapper on top of the Core Data boilerplate, it encapsulates dealing with NSPersistentStoreCoordinator and NSManageObjectContexts.\n\n```swift\nself.dataStack = DataStack(modelName: \"DataModel\")\n```\n\n[You can find here more ways of initializing your DataStack](https://github.com/3lvis/Sync/blob/6723c1f9a07014024e0f8f2923d1930789cabb72/Source/DataStack/DataStack.swift#L77-L196).\n\n### Sync\n\n```swift\ndataStack.sync(json, inEntityNamed: \"User\") { error in\n    // New objects have been inserted\n    // Existing objects have been updated\n    // And not found objects have been deleted\n}\n```\n\nAlternatively, if you only want to sync users that have been created in the last 24 hours, you could do this by using a `NSPredicate`.\n\n```swift\nlet now = NSDate()\nlet yesterday = now.dateByAddingTimeInterval(-24*60*60)\nlet predicate = NSPredicate(format:@\"createdAt \u003e %@\", yesterday)\n\ndataStack.sync(json, inEntityNamed: \"User\", predicate: predicate) { error in\n    //..\n}\n```\n\n## Demo Project\n\n[We have a simple demo project](/iOSDemo) of how to set up and use Sync to fetch data from the network and display it in a UITableView. The demo project features both [Networking](https://github.com/3lvis/networking) and [Alamofire](https://github.com/Alamofire/Alamofire) as the networking libraries.\n\n### DataStack with Storyboards\n\nConfiguring a DataStack with Storyboard is different than doing it via dependency injection here you'll find a sample project in how to achieve this setup.\n\nhttps://github.com/3lvis/StoryboardDemo\n\n## Getting Started\n\n### Core Data Stack\n\nReplace your Core Data stack with an instance of [DataStack](https://github.com/3lvis/Sync/blob/master/docs/DataStack.md).\n\n```swift\nself.dataStack = DataStack(modelName: \"Demo\")\n```\n\n### Primary key\n\nSync requires your entities to have a primary key, this is important for diffing, otherwise Sync doesn’t know how to differentiate between entries.\n\nBy default **Sync** uses `id` from the JSON and `id` (or `remoteID`) from Core Data as the primary key.\n\nYou can mark any attribute as primary key by adding `sync.isPrimaryKey` and the value `true` (or `YES`). For example, in our [Designer News](https://github.com/3lvis/DesignerNewsDemo) project we have a `Comment` entity that uses `body` as the primary key.\n\n![Custom primary key](https://raw.githubusercontent.com/3lvis/Sync/master/Images/custom-primary-key-v3.png)\n\nIf you add the flag `sync.isPrimaryKey` to the attribute `contractID` then:\n\n- Local primary key will be: `contractID`\n- Remote primary key will be: `contract_id`\n\nIf you want to use `id` for the remote primary key you also have to add the flag `sync.remoteKey` and write `id` as the value.\n\n- Local primary key will be: `articleBody`\n- Remote primary key will be: `id`\n\n### Attribute Mapping\n\nYour attributes should match their JSON counterparts in `camelCase` notation instead of `snake_case`. For example `first_name` in the JSON maps to `firstName` in Core Data and `address` in the JSON maps to `address` in Core Data.\n\nThere are some exception to this rule:\n\n* Reserved attributes should be prefixed with the `entityName` (`type` becomes `userType`, `description` becomes `userDescription` and so on). In the JSON they don't need to change, you can keep `type` and `description` for example. A full list of reserved attributes can be found [here](https://github.com/3lvis/Sync/blob/master/Source/PropertyMapper/NSManagedObject%2BPropertyMapperHelpers.m#L282-L284)\n* Attributes with acronyms will be normalized (`id`, `pdf`, `url`, `png`, `jpg`, `uri`, `json`, `xml`). For example `user_id` will be mapped to `userID` and so on. You can find the entire list of supported acronyms [here](https://github.com/3lvis/Sync/blob/master/Source/Inflections/Inflections.m#L204-L206).\n\nIf you want to map your Core Data attribute with a JSON attribute that has different naming, you can do by adding `sync.remoteKey` in the user info box with the value you want to map.\n\n![Custom remote key](https://raw.githubusercontent.com/3lvis/Sync/master/Images/custom-remote-key-v2.png)\n\n### Attribute Types\n\n#### Array/Dictionary\n\nTo map **arrays** or **dictionaries** just set attributes as `Binary Data` on the Core Data modeler.\n\n![screen shot 2015-04-02 at 11 10 11 pm](https://cloud.githubusercontent.com/assets/1088217/6973785/7d3767dc-d98d-11e4-8add-9c9421b5ed47.png)\n\n#### Retrieving mapped arrays\n\n```json\n{\n  \"hobbies\": [\n    \"football\",\n    \"soccer\",\n    \"code\"\n  ]\n}\n```\n\n```swift\nlet hobbies = NSKeyedUnarchiver.unarchiveObjectWithData(managedObject.hobbies) as? [String]\n// ==\u003e \"football\", \"soccer\", \"code\"\n```\n\n#### Retrieving mapped dictionaries\n```json\n{\n  \"expenses\" : {\n    \"cake\" : 12.50,\n    \"juice\" : 0.50\n  }\n}\n```\n\n```swift\nlet expenses = NSKeyedUnarchiver.unarchiveObjectWithData(managedObject.expenses) as? [String: Double]\n// ==\u003e \"cake\" : 12.50, \"juice\" : 0.50\n```\n\n#### Dates\n\nWe went for supporting [ISO8601](http://en.wikipedia.org/wiki/ISO_8601) and unix timestamp out of the box because those are the most common formats when parsing dates, also we have a [quite performant way to parse this strings](https://github.com/3lvis/Sync/blob/master/Source/DateParser/NSDate%2BPropertyMapper.m) which overcomes the [performance issues of using `NSDateFormatter`](http://blog.soff.es/how-to-drastically-improve-your-app-with-an-afternoon-and-instruments/).\n\n```swift\nlet values = [\"created_at\" : \"2014-01-01T00:00:00+00:00\",\n              \"updated_at\" : \"2014-01-02\",\n              \"published_at\": \"1441843200\"\n              \"number_of_attendes\": 20]\n\nmanagedObject.fill(values)\n\nlet createdAt = managedObject.value(forKey: \"createdAt\")\n// ==\u003e \"2014-01-01 00:00:00 +00:00\"\n\nlet updatedAt = managedObject.value(forKey: \"updatedAt\")\n// ==\u003e \"2014-01-02 00:00:00 +00:00\"\n\nlet publishedAt = managedObject.value(forKey: \"publishedAt\")\n// ==\u003e \"2015-09-10 00:00:00 +00:00\"\n```\n\n### Relationship mapping\n\n**Sync** will map your relationships to their JSON counterparts. In the [Example](#example-with-snake_case-in-swift) presented at the beginning of this document you can see a very basic example of relationship mapping.\n\n#### One-to-many\n\nLets consider the following Core Data model.\n\n![One-to-many](https://raw.githubusercontent.com/3lvis/Sync/master/Images/one-to-many-swift.png)\n\nThis model has a one-to-many relationship between `User` and `Note`, so in other words a user has many notes. Here can also find an inverse relationship to user on the Note model. This is required for Sync to have more context on how your models are presented. Finally, in the Core Data model there is a cascade relationship between user and note, so when a user is deleted all the notes linked to that user are also removed (you can specify any delete rule).\n\nSo when Sync, looks into the following JSON, it will sync all the notes for that specific user, doing the necessary inverse relationship dance.\n\n```json\n[\n  {\n    \"id\": 6,\n    \"name\": \"Shawn Merrill\",\n    \"notes\": [\n      {\n        \"id\": 0,\n        \"text\": \"Shawn Merril's diary, episode 1\",\n      }\n    ]\n  }\n]\n```\n\n#### One-to-many Simplified\n\nAs you can see this procedures require the full JSON object to be included, but when working with APIs, sometimes you already have synced all the required items. Sync supports this too.\n\nFor example, in the one-to-many example, you have a user, that has many notes. If you already have synced all the notes then your JSON would only need the `notes_ids`, this can be an array of strings or integers. As a side-note only do this if you are 100% sure that all the required items (notes) have been synced, otherwise this relationships will get ignored and an error will be logged. Also if you want to remove all the notes from a user, just provide `\"notes_ids\": null` and **Sync** will do the clean up for you.\n\n```json\n[\n  {\n    \"id\": 6,\n    \"name\": \"Shawn Merrill\",\n    \"notes_ids\": [0, 1, 2]\n  }\n]\n```\n\n#### One-to-one\n\nA similar procedure is applied to one-to-one relationships. For example lets say you have the following model:\n\n![one-to-one](https://raw.githubusercontent.com/3lvis/Sync/master/Images/one-to-one-v2.png)\n\nThis model is simple, a user as a company. A compatible JSON would look like this:\n\n```json\n[\n  {\n    \"id\": 6,\n    \"name\": \"Shawn Merrill\",\n    \"company\": {\n      \"id\": 0,\n      \"text\": \"Facebook\",\n    }\n  }\n]\n```\n\n#### One-to-one Simplified\n\nAs you can see this procedures require the full JSON object to be included, but when working with APIs, sometimes you already have synced all the required items. Sync supports this too.\n\nFor example, in the one-to-one example, you have a user, that has one company. If you already have synced all the companies then your JSON would only need the `company_id`. As a sidenote only do this if you are 100% sure that all the required items (companies) have been synced, otherwise this relationships will get ignored and an error will be logged. Also if you want to remove the company from the user, just provide `\"company_id\": null` and **Sync** will do the clean up for you.\n\n```json\n[\n  {\n    \"id\": 6,\n    \"name\": \"Shawn Merrill\",\n    \"company_id\": 0\n  }\n]\n```\n\n## JSON Exporting\n\nSync provides an easy way to convert your NSManagedObject back into JSON. Just use the `export()` method.\n\n``` objc\nlet user = //...\nuser.set(value: \"John\" for: \"firstName\")\nuser.set(value: \"Sid\" for: \"lastName\")\n\nlet userValues = user.export()\n```\n\nThat's it, that's all you have to do, the keys will be magically transformed into a `snake_case` convention.\n\n```json\n{\n  \"first_name\": \"John\",\n  \"last_name\": \"Sid\"\n}\n```\n\n### Excluding\n\nIf you don't want to export certain attribute or relationship, you can prohibit exporting by adding `sync.nonExportable` in the user info of the excluded attribute or relationship.\n\n![non-exportable](https://raw.githubusercontent.com/3lvis/Sync/master/Images/pm-non-exportable.png)\n\n### Relationships\n\nIt supports exporting relationships too.\n\n```json\n\"first_name\": \"John\",\n\"last_name\": \"Sid\",\n\"notes\": [\n  {\n    \"id\": 0,\n    \"text\": \"This is the text for the note A\"\n  },\n  {\n    \"id\": 1,\n    \"text\": \"This is the text for the note B\"\n  }\n]\n```\n\nIf you don't want relationships you can also ignore relationships:\n\n```swift\nlet dictionary = user.export(using: .excludedRelationships)\n```\n\n```json\n\"first_name\": \"John\",\n\"last_name\": \"Sid\"\n```\n\nOr get them as nested attributes, something that Ruby on Rails uses (`accepts_nested_attributes_for`), for example for a user that has many notes:\n\n```swift\nvar exportOptions = ExportOptions()\nexportOptions.relationshipType = .nested\nlet dictionary = user.export(using: exportOptions)\n```\n\n```json\n\"first_name\": \"John\",\n\"last_name\": \"Sid\",\n\"notes_attributes\": [\n  {\n    \"0\": {\n      \"id\": 0,\n      \"text\": \"This is the text for the note A\"\n    },\n    \"1\": {\n      \"id\": 1,\n      \"text\": \"This is the text for the note B\"\n    }\n  }\n]\n```\n\n## FAQ\n\n[Check our FAQ document.](https://github.com/3lvis/Sync/blob/master/docs/faq.md)\n\n## Installation\n\n### CocoaPods\n\n```ruby\npod 'Sync', '~\u003e 6'\n```\n\n### Carthage\n\n```ruby\ngithub \"3lvis/Sync\" ~\u003e 6.0\n```\n\n### Supported iOS, OS X, watchOS and tvOS Versions\n\n- iOS 8 or above\n- OS X 10.10 or above\n- watchOS 2.0 or above\n- tvOS 9.0 or above\n\n## Backers\n\nFinding Sync helpful? Consider supporting further development and support by becoming a sponsor:\n👉  https://github.com/sponsors/3lvis\n\n\n## License\n\n**Sync** is available under the MIT license. See the [LICENSE](https://github.com/3lvis/Sync/blob/master/LICENSE.md) file for more info.\n","funding_links":["https://github.com/sponsors/3lvis"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F3lvis%2Fsync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F3lvis%2Fsync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F3lvis%2Fsync/lists"}