Documents

      +

      Description — Couchbase Lite concepts — Data model — Documents
      Related Content — Databases | Blobs | Indexing |

      Overview

      Document Structure

      In Couchbase Lite the term 'document' refers to an entry in the database. You can compare it to a record, or a row in a table.

      Each document has an ID or unique identifier. This ID is similar to a primary key in other databases.

      You can specify the ID programmatically. If you omit it, it will be automatically generated as a UUID.

      the ID must be unique within the database. You cannot change it after you have written the document.

      The document also has a value which contains the actual application data. This value is stored as a dictionary collection of key-value (k-v) pairs. The values can be made of up several different Data Types such as numbers, strings, arrays, and nested objects.

      Data Encoding

      The document body is stored in an internal, efficient, binary form called Fleece. This internal form can be easily converted into a manageable native dictionary format for manipulation in applications.

      Fleece data is stored in the smallest format that will hold the value whilst maintaining the integrity of the value.

      Data Types

      The Document class offers a set of property accessors for various scalar types, such as:

      • Boolean

      • Date

      • Double

      • Float

      • Int

      • Long

      • String

      These accessors take care of converting to/from JSON encoding, and make sure you get the type you expect.

      In addition to these basic data types Couchbase Lite provides for the following:

      Dictionary

      represents a read-only key-value pair collection

      MutableDictionary

      represents a writeable key-value pair collection

      Array

      represents a readonly ordered collection of objects

      MutableArray

      represents a writeable collection of objects

      Blob

      represents an arbitrary piece of binary data

      JSON

      Couchbase Lite also provides for the direct handling of JSON data implemented in most cases by the provision of a toJSON() method on appropriate API classes (for example, on MutableDocument, Dictionary, Blob and Array) — see Working with JSON Data.

      Constructing a Document

      An individual document often represents a single instance of an object in application code.

      You can consider a document as the equivalent of a 'row' in a relational table, with each of the document’s attributes being equivalent to a 'column'.

      Documents can contain nested structures. This allows developers to express many-to-many relationships without requiring a reference or join table, and is naturally expressive of hierarchical data.

      Most apps will work with one or more documents, persisting them to a local database and optionally syncing them, either centrally or to the cloud.

      In this section we provide an example of how you might create a hotel document, which provides basic contact details and price data.

      Data Model
      hotel: {
        type: string (value = `hotel`)
        name: string
        address: dictionary {
          street: string
          city: string
          state: string
          country: string
          code: string
        }
        phones: array
        rate: float
      }

      Open a Database

      First open your database. If the database does not already exist, Couchbase Lite will create it for you.

      // Get the database (and create it if it doesn’t exist).
      let database = try!Database(name: "hoteldb");

      See Databases for more information

      Create a Document

      Now create a new document to hold your application’s data.

      Use the mutable form, so that you can add data to the document.

      // Create your new document
      // The lack of 'const' indicates this document is mutable
      var mutableDoc = MutableDocument(id: "doc1");

      For more on using Documents, see Document Initializers and Mutability.

      Create a Dictionary

      Now create a mutable dictionary (address).

      Each element of the dictionary value will be directly accessible via its own key.

      // Create and populate mutable dictionary
      // Create a new mutable dictionary and populate some keys/values
      var address = MutableDictionaryObject();
      address.setString("1 Main st.", forKey: "street");
      address.setString("San Francisco", forKey: "city");
      address.setString("CA", forKey: "state");
      address.setString("USA", forKey: "country");
      address.setString("90210", forKey: "code");

      Learn more about Using Dictionaries.

      Create an Array

      Since the hotel may have multiple contact numbers, provide a field (phones) as a mutable array.

      // Create and populate mutable array
      var phones = MutableArrayObject();
      phones.addString("650-000-0000");
      phones.addString("650-000-0001");

      Learn more about Using Arrays

      Populate a Document

      Now add your data to the mutable document created earlier. Each data item is stored as a key-value pair.

      // Initialize and populate the document
      
      // Add document type and hotel name as string
      mutableDoc.setString("hotel", forKey:"type");
      mutableDoc.setString("Hotel Java Mo", forKey:"name");
      
      // Add average room rate (float)
      mutableDoc.setFloat(121.75, forKey:"room_rate");
      
      // Add address (dictionary)
      mutableDoc.setDictionary(address, forKey: "address");
      
      // Add phone numbers(array)
      mutableDoc.setArray(phones, forKey:"phones");
      Couchbase recommend using a type attribute to define each logical document type.

      Save a Document

      Now persist the populated document to your Couchbase Lite database. This will auto-generate the document id.

      try!database.saveDocument(mutableDoc);

      Close the Database

      With your document saved, you can now close our Couchbase Lite database.

      do {
          try database.close()
      } catch {
          print(error)
      }

      Working with Data

      Checking a Document’s Properties

      To check whether a given property exists in the document, use the Document.Contains(key:) method.

      If you try to access a property which doesn’t exist in the document, the call will return the default value for that getter method (0 for Document.int() 0.0 for Document.float() etc.).

      Date accessors

      Couchbase Lite offers Date accessors as a convenience. Dates are a common data type, but JSON doesn’t natively support them, so the convention is to store them as strings in ISO-8601 format.

      Example 1. Date Getter

      This example sets the date on the createdAt property and reads it back using the Document.date() accessor method.

      newTask.setValue(Date(), forKey: "createdAt")
      let date = newTask.date(forKey: "createdAt")

      Using Dictionaries

      Example 2. Read Only
      // NOTE: No error handling, for brevity (see getting started)
      let document = database?.document(withID:"doc1");
      
      // Getting a dictionary from the document's properties
      let dict = document?.dictionary(forKey: "address");
      
      // Access a value with a key from the dictionary
      let street = dict?.string(forKey: "street");
      
      // Iterate dictionary
      for key in dict!.keys {
          print("Key \(key) = \(dict!.value(forKey:key))");
      }
      
      // Create a mutable copy
      let mutable_dict = dict?.toMutable();
      Example 3. Mutable
      // Create a new mutable dictionary and populate some keys/values
      var mutable_dict = MutableDictionaryObject();
      mutable_dict.setString("1 Main st.", forKey: "street");
      mutable_dict.setString("San Francisco", forKey: "city");
      
      // Add the dictionary to a document's properties and save the document
      var mutable_doc = MutableDocument(id: "doc1");
      mutable_doc.setDictionary(mutable_dict, forKey: "address");
      try!database.saveDocument(mutable_doc);

      Using Arrays

      Example 4. Read Only
      let document = database.document(withID:"doc1");
      
      // Getting a phones array from the document's properties
      let array = document?.array(forKey: "phones")
      
      // Get element count
      let count = array!.count;
      
      // Access an array element by index
      if count >= 0 { let phone = array![1]; }
      
      // Iterate dictionary
      for (index, element) in array!.enumerated() {
          print("Index \(index) = \(element)");
      }
      
      // Create a mutable copy
      var mutable_array = array!.toMutable();
      Example 5. Mutable
      // Create a new mutable array and populate data into the array
      var mutable_array = MutableArrayObject();
      mutable_array.addString("650-000-0000");
      mutable_array.addString("650-000-0001");
      
          // Set the array to document's properties and save the document
      var mutable_doc = MutableDocument(id: "doc1");
      mutable_doc.setArray(mutable_array, forKey:"phones");
      try!database.saveDocument(mutable_doc);

      Using Blobs

      For more on working with blobs, see Blobs

      Document Initializers

      You can use the following methods/initializers:

      • Use the MutableDocument() initializer to create a new document where the document ID is randomly generated by the database.

      • Use the MutableDocument(String id) initializer to create a new document with a specific ID.

      • Use the Database.document(withID:) method to get a document. If the document doesn’t exist in the database, the method will return null. You can use this behavior to check if a document with a given ID already exists in the database.

      Example 6. Persist a document

      The following code example creates a document and persists it to the database.

      let newTask = MutableDocument()
          .setString("task", forKey: "type")
          .setString("todo", forKey: "owner")
          .setDate(Date(), forKey: "createdAt")
      try database.saveDocument(newTask)

      Mutability

      By default, a document is immutable when it is read from the database. Use the Document.toMutable() to create an updatable instance of the document.

      Example 7. Make a mutable document

      Changes to the document are persisted to the database when the save method is called.

      guard let document = database.document(withID: "xyz") else { return }
      let mutableDocument = document.toMutable()
      mutableDocument.setString("apples", forKey: "name")
      try database.saveDocument(mutableDocument)
      Any user change to the value of reserved keys (_id, _rev or _deleted) will be detected when a document is saved and will result in an exception (Error Code 5 — CorruptRevisionData) — see also Document Constraints.

      Document Conversion

      You can convert a Document to a plain dictionary type and/or to a JSON string. This can often be useful to pass the document contents as a plain object to another method.

      Example 8. Convert document
      print(newTask.toDictionary())
      
      print(newTask.toJSON())

      Batch operations

      If you’re making multiple changes to a database at once, it’s faster to group them together. The following example persists a few documents in batch.

      Example 9. Batch operations
      do {
          try database.inBatch {
              for i in 0...10 {
                  let doc = MutableDocument()
                  doc.setValue("user", forKey: "type")
                  doc.setValue("user \(i)", forKey: "name")
                  doc.setBoolean(false, forKey: "admin")
                  try database.saveDocument(doc)
                  print("saved user document \(doc.string(forKey: "name")!)")
              }
          }
      } catch let error {
          print(error.localizedDescription)
      }

      At the local level this operation is still transactional: no other Database instances, including ones managed by the replicator can make changes during the execution of the block, and other instances will not see partial changes. But Couchbase Mobile is a distributed system, and due to the way replication works, there’s no guarantee that Sync Gateway or other devices will receive your changes all at once.

      Document change events

      You can register for document changes. The following example registers for changes to the document with ID user.john and prints the verified_account property when a change is detected.

      Example 10. Document change events
      database.addDocumentChangeListener(withID: "user.john") { (change) in
          if let document = self.database.document(withID: change.documentID) {
              print("Status :: \(document.string(forKey: "verified_account")!)")
          }
      }

      Document Expiration

      Document expiration allows users to set the expiration date for a document. When the document expires, it is purged from the database. The purge is not replicated to Sync Gateway.

      Example 11. Set document expiration

      This example sets the TTL for a document to 5 minutes from the current time.

      // Purge the document one day from now
      let ttl = Calendar.current.date(byAdding: .day, value: 1, to: Date())
      try database.setDocumentExpiration(withID: "doc123", expiration: ttl)
      
      // Reset expiration
      try database.setDocumentExpiration(withID: "doc1", expiration: nil)
      
      // Query documents that will be expired in less than five minutes
      let fiveMinutesFromNow = Date(timeIntervalSinceNow: 60 * 5).timeIntervalSince1970
      let query = QueryBuilder
          .select(SelectResult.expression(Meta.id))
          .from(DataSource.database(database))
          .where(
              Meta.expiration.lessThan(
                  Expression.double(fiveMinutesFromNow)
              )
          )

      Document Constraints

      Couchbase Lite APIs do not explicitly disallow the use of attributes with the underscore prefix at the top level of document. This is to facilitate the creation of documents for use either in local only mode where documents are not synced, or when used exclusively in peer-to-peer sync.

      "_id", :"_rev" and "_sequence" are reserved keywords and must not be used as top-level attributes — see Example 12.

      Users are cautioned that any attempt to sync such documents to Sync Gateway will result in an error. To be future proof, you are advised to avoid creating such documents. Use of these attributes for user-level data may result in undefined system behavior.

      For more guidance — see: Sync Gateway - data modeling guidelines

      Example 12. Reserved Keys List
      • _attachments

      • _deleted [1]

      • _id [1]

      • _removed

      • _rev [1]

      • _sequence

      Working with JSON Data

      The toJSON() typed-accessor means you can easily work with JSON data, native and Couchbase Lite objects.

      Arrays

      Convert an ArrayObject to and from JSON using the toJSON() and toArray methods — see Example 4.

      Additionally you can:

      • Initialize a 'MutableArrayObject' using data supplied as a JSON string. This is done using the init(json) constructor — see: Example 4

      • Convert an ArrayFragment object to a JSON String

      • Set data with a JSON string using setJSON()

      Example 13. Arrays as JSON strings
      if let doc = database.document(withID: "1000") {
          guard let array = doc.array(forKey: "list") else {
              return
          }
      
          let json = array.toJSON()
          print(json)
      }

      Blobs

      Convert a Blob to JSON using the toJSON method — see Example 14.

      You can use isBlob() to check whether a given dictionary object is a blob or not — see Example 14.

      Note that the blob object must first be saved to the database (generating the required metadata) before you can use the toJSON method.

      Example 14. Blobs as JSON strings
      // Get a document
      if let doc = database.document(withID: "1000") {
          guard let blob = doc.blob(forKey: "avatar") else {
              return
          }
      
          let json = blob.toJSON()
          print(json)
      }

      See also: Blobs

      Dictionaries

      Convert a DictionaryObject to and from JSON using the toJSON and toDictionary methods — see Example 15.

      Additionally you can:

      • Initialize a 'MutableDictionaryObject' using data supplied as a JSON string. This is done using the init(json) constructor-- see: Example 15

      • Set data with a JSON string using setJSON()

      Example 15. Dictionaries as JSON strings
      if let doc = database.document(withID: "1000") {
          guard let dictionary = doc.dictionary(forKey: "dictionary") else {
              return
          }
      
          let json = dictionary.toJSON()
          print(json)
      }

      Documents

      Convert a Document to and from JSON strings using the toJSON() and setJSON() methods — see Example 16.

      Additionally you can:

      • Initialize a 'MutableDocument' using data supplied as a JSON string. This is done using the init(json) or init(id: json:) constructor — see: Example 16

      • Set data with a JSON string using setJSON()

      Example 16. Documents as JSON strings
      let database = try Database(name: "hotel")
      let query = QueryBuilder
          .select(SelectResult.expression(Meta.id).as("metaId"))
          .from(DataSource.database(database))
      
      if let doc = database.document(withID: "doc-id") {
          let json = doc.toJSON()
          print(json)
      }

      Query Results as JSON

      Convert a Query Result to JSON using its toJSON() accessor method.

      Example 17. Using JSON Results

      Use result.toJSON() to transform your result string into a JSON string, which can easily be serialized or used as required in your application. See <> for a working example.

      
      // In this example the Hotel class is defined using Codable
      //
      // class Hotel : Codable {
      //   var id : String = "undefined"
      //   var type : String = "hotel"
      //   var name : String = "undefined"
      //   var city : String = "undefined"
      //   var country : String = "undefined"
      //   var description : String? = ""
      //   var text : String? = ""
      //   ... other class content
      // }
      
      let results = try query.execute()
      for row in  results {
      
          // get the result into a JSON String
          let jsonString = row.toJSON()
      
          let thisJsonObj:Dictionary =
          try (JSONSerialization.jsonObject(
              with: jsonString.data(using: .utf8)!,
              options: .allowFragments)
               as? [String: Any])!
      
          // Use Json Object to populate Native object
          // Use Codable class to unpack JSON data to native object
          var this_hotel: Hotel = try JSONDecoder().decode(Hotel.self, from: jsonString.data(using: .utf8)!) (1)
      
          // ALTERNATIVELY unpack in steps
          this_hotel.id = thisJsonObj["id"] as! String
          this_hotel.name = thisJsonObj["name"] as! String
          this_hotel.type = thisJsonObj["type"] as! String
          this_hotel.city = thisJsonObj["city"] as! String
          hotels[this_hotel.id] = this_hotel
      
      } // end for
      JSON String Format

      If your query selects ALL then the JSON format will be:

      {
        database-name: {
          key1: "value1",
          keyx: "valuex"
        }
      }

      If your query selects a sub-set of available properties then the JSON format will be:

      {
        key1: "value1",
        keyx: "valuex"
      }

      1. Any change to this reserved key will be detected when it is saved and will result in a Couchbase exception (Error Code 5 — `CorruptRevisionData`)