Giter Club home page Giter Club logo

jsoncache's Introduction

JSONCache

JSONCache is a thin layer on top of Core Data that seamlessly consumes, caches and produces JSON data.

  • Automatic mapping between camelCase and snake_case.
  • Automatic relationship mapping, based on inferred knowledge of your Core Data model.
  • Automatic merging of JSON data into existing objects.
  • On-demand JSON generation, both from NSManageObject instances, and from any struct that adopts the JSONifiable protocol.
  • All JSON parsing and Core Data operations are done in the background, so it doesn't interfere with your app's responsiveness.

Content

Show, don't tell

Consuming JSON

Say your backend produces JSON like this:

{
  "bands": [
    {
      "name": "Japan",
      "formed": 1974,
      "disbanded": 1991,
      "hiatus": "1982-1989",
      "description": "Initially a glam-inspired group [...]"
    },
    ...
  ],
  "band_members": [
    {
      "id": "David Sylvian in Japan",
      "musician": "David Sylvian",
      "band": "Japan",
      "instruments": "Lead vocals, keyboards, guitar",
      "joined": 1974,
      "left": 1991
    },
    ...
  ],
  "musicians": [
    {
      "name": "David Sylvian",
      "born": 1958,
      "instruments": "Vocals, guitar, keyboards"
    },
    ...
  ],
  "albums": [
    {
      "name": "Gentlemen Take Polaroids",
      "band": "Japan",
      "released": "1980-10-24T00:00:00Z",
      "label": "Virgin"
    },
    ...
  ]
}

To cache this data in your app, you create a suitable Core Data model:

And with only a few lines of code, the JSON data is safely persisted in Core Data on your device, relationships and all:

import JSONCache

...

let jsonObject = try! JSONSerialization.jsonObject(with: jsonData) as! [String: Any]
let bands = jsonObject["bands"] as! [[String: Any]]
let bandMembers = jsonObject["band_members"] as! [[String: Any]]
let musicians = jsonObject["musicians"] as! [[String: Any]]
let albums = jsonObject["albums"] as! [[String: Any]]
        
JSONCache.casing = .snake_case
JSONCache.dateFormat = .iso8601WithSeparators
        
JSONCache.bootstrap(withModelName: "Bands") { result in
  switch result {
  case .success:
    JSONCache.stageChanges(withDictionaries: bands, forEntityWithName: "Band")
    JSONCache.stageChanges(withDictionaries: bandMembers, forEntityWithName: "BandMember")
    JSONCache.stageChanges(withDictionaries: musicians, forEntityWithName: "Musician")
    JSONCache.stageChanges(withDictionaries: albums, forEntityWithName: "Album")
    JSONCache.applyChanges { result in
      switch result {
      case .success:
        print("Data all nicely tucked in")
      case .failure(let error):
        print("An error occurred: \(error)")
      }
    }
  case .failure(let error):
    print("An error occurred: \(error)")
  }
}

If you receive additional data at a later stage it's even simpler:

let albums = jsonObject["albums"] as! [[String: Any]]

JSONCache.stageChanges(withDictionaries: albums, forEntityWithName: "Album")
JSONCache.applyChanges { result in
  switch result {
  case .success:
    print("Data all nicely tucked in")
  case .failure(let error):
    print("An error occurred: \(error)")
  }
}

Producing JSON

If your app allows producing as well as consuming data, you can generate JSON directly from NSManagedObject instances:

switch JSONCache.fetchObject(ofType: "Band", withId: "Japan") {
  case .success(let japan):
    var japan = japan as! Band
    japan.otherNames = "Rain Tree Crow"
    
    ServerProxy.update(band: japan.toJSONDictionary()) { result in
      switch result {
      case .success:
        switch JSONCache.save() {
          case .success:
            print("Japan as Rain Tree Crow all nicely tucked in")
          case .failure(let error):
            print("An error occurred: \(error)")
        }
      case .failure(let error):
        print("An error occurred: \(error)")
      }
    }
  case .failure(let error):
    print("An error occurred: \(error)")
}

To create and persist new objects to the backend, you can either create the NSManagedObject instance first and then use it to generate JSON for the backend, or if you prefer to wait until the record is safely persisted on the backend, you can generate JSON from any struct that adopts the JSONifiable protocol:

struct BandInfo: JSONifiable {
  var name: String
  var bandDescription: String
  var formed: Int
  var disbanded: Int?
  var hiatus: Int?
  var otherNames: String?
}

let u2Info = BandInfo(name: "U2", bandDescription: "Dublin boys", formed: 1976, disbanded: nil, hiatus: nil, otherNames: "Passengers")

ServerProxy.save(band: u2Info.toJSONDictionary()) { result in
  switch result {
  case .success:
    u2 = NSEntityDescription.insertNewObject(forEntityName: "Band" into: JSONCache.mainContext)!
    u2.setAttributes(fromDictionary: u2Info)
    
    switch JSONCache.save() {
    case .success:
      print("U2 all nicely tucked in")
    case .failure(let error)
      print("An error occurred: \(error)")
    }
  case .failure(let error):
    print("An error occurred: \(error)")
}

But do tell

Case conversion

Before JSON data is loaded into Core Data, any necessary case conversion is performed on the attribute names. The JSONCache.casing configuration parameter tells JSONCache whether to expect .snake_case or .camelCase in the JSON data. Case conversion is only done if the JSON casing is .snake_case:

  • attribute_name becomes attributeName.
  • description, being a reserved attribute name, becomes entityNameDescription.

Similarly, when producing JSON:

  • attributeName becomes attribute_name.
  • entityNameDescription becomes description.

Date conversion

JSONCache supports the following JSON date formats:

  • ISO 8601 with separators: 2000-08-22T13:28:00Z
  • ISO 8601 without separators: 20000822T132800Z
  • Seconds since 00:00 on 1 Jan 1970 as a double precision value: 966950880.0

Use the JSONCache.dateFormat configuration parameter to tell JSONCache which format to expect and/or produce.

Relationship mapping

For JSONCache to automatically map relationships, two things must be in place:

  1. A primary key for each entity that takes part in a relationship.
  2. The foreign key of the target entity in the JSON relationship attribute on the 'one end' of one-to-one and one-to-many relationships.

Many-to-many relationships are currently not supported, except through relationship entities (such as the BandMember entity in the JSON example above).

Entity primary key

For JSONCache to automatically map relationships, you must mark the primary key of each entity in your Core Data model. You do this in either of two ways:

  1. Use the name id for the primary key. (See Figure 1.)
  2. Create a User Info key named DC.isIdentifier for the primary key attribute and assign it the value true or YES (both case insensitive). (See Figure 2.)

The primary key must be unique within an entity, but not across entities.

Figure 1: Marking an entity's primary key by naming it id.

Figure 2: Marking an entity's primary key by creating a User Info key named DC.isIdentifier and setting it to true.

JSON foreign key

Consider the following JSON records:

Musician

{
  "name": "Mick Karn",  <- Primary key
  "born": 1958,
  "dead": 2011,
  "instruments": "Bass, sax, clarinet, oboe, keyboards, vocals"
}

BandMember

{
  "id": "Mick Karn in Japan",  <- Primary key
  "musician": "Mick Karn",     <- Foreign key
  "band": "Japan",             <- Foreign key
  "joined": 1974,
  "left": 1991,
  "instruments": "Bass, sax, vocals"
}

Band

{
  "name": "Japan",  <- Primary key
  "formed": 1974,
  "disbanded": 1991,
  "hiatus": "1982-1989",
  "description": "Initially a glam-inspired group [...]",
  "other_names": "Rain Tree Crow"
}

Album

{
  "name": "Tin Drum",
  "band": "Japan",  <- Foreign key
  "released": "1981-11-13T00:00:00Z",
  "label": "Virgin"
}

The foreign keys in the JSON data correspond to toOne relationships in Core Data. JSONCache retrieves the NSRelationshipDescription for each relationship, uses this to obtain the class of the target object, looks it up using the foreign key from the JSON dictionary, and establishes the relationship.

Installation

You can install JSONCache using either CocoaPods or Carthage.

CocoaPods

pod 'JSONCache'

Carthage

github "andersblehr/JSONCache" ~> 1.0

Compatibility

  • iOS 9.3 or later
  • Swift 3.x

Support for other Apple platforms is in the works.

License

JSONCache is released under the MIT license. See the LICENSE file for details.

jsoncache's People

Contributors

andersblehr avatar

Watchers

Carabineiro avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.