Instant JSON

Instant JSON is our approach to bringing annotations into a modern format while keeping all important properties to make the Instant JSON spec work with PDF. It’s fully documented and supports long-term storage.

Instant JSON stores PDF changes like annotations in a separate JSON file. This means that a PDF document will only need to be transferred once and all changes will be added as an overlay to the existing PDF. This approach significantly reduces the bandwidth since you only need to transfer the JSON instead of the complete PDF.

Conceptually, Instant JSON defines a list of skippedPdfObjectIds. These point to the PDF’s internal object IDs for annotations. Whenever an object ID is marked as skipped, it’ll no longer be loaded from the original PDF. Instead, it could be defined inside the annotations array with the same pdfObjectId. If this is the case, the PDF viewer will display the new annotation, which signals an update to the original one. If an object ID is marked as skipped but the annotations array doesn’t contain an annotation with the same pdfObjectId, it’ll be interpreted as a deleted annotation. An annotation inside the annotations array without the pdfObjectId property is interpreted as a newly created annotation.

All annotations in the annotations array have a unique id field. For updated annotations that were in the original PDF, this field will be the stringified pdfObjectId. Newly created annotations will get a newly generated ULID.

An “empty” Instant JSON contains neither skippedPdfObjectIds nor annotations, which means the original PDF is untouched. All annotations in the initial PDF are still shown.

The Format

We use Flow type declarations to specify the format of Instant JSON:

declare type InstantJSON = {
  format: "https://pspdfkit.com/instant-json/v1",
  pdfId?: {
    permanent: string,
    changing: string
  },
  skippedPdfObjectIds?: number[],
  annotations?: Object[],
  formFieldValues?: Object[]
};

format

This is a literal string that includes the version information.

pdfId

This optional key contains an object of a permanent PDF ID and a changing PDF ID. According to the PDF spec, a PDF document must contain these IDs. We use the permanent ID to verify that the PDF you’ve opened together with this Instant JSON is indeed the correct one. The changing PDF ID will be updated whenever the PDF file is updated (for example, when saved with different annotations). Since Instant JSON only works with an immutable PDF, the state will be invalid when used with a changed PDF.

Not every PDF will have a valid permanent or changing ID. As such, this field might not be set. We recommend you take care to always use the same PDF.

skippedPdfObjectIds

This is an array of PDF object IDs that will be ignored when importing annotations from the original PDF document. If this array is empty, the key shouldn’t be set.

annotations

This is a list of new or updated annotations. Annotations follow the format for Instant JSON for annotations. When an annotation contains a pdfObjectId, it’s considered to be an update to one of the annotations of the original PDF. For newly created annotations, this key won’t be set.

formFieldValues

This is a list of modified form field values. Objects follow the Instant form field value JSON format. This list won’t be preset when no form field values have been modified.

Instant Annotation JSON API

Annotation JSON is Instant’s representation of a single annotation. To serialize an Annotation to its JSON representation, you can call annotation.toInstantJson(). For example:

// Serializes a single annotation to a JSON string.
val json: String = annotation.toInstantJson()
// Serializes a single annotation to a JSON string.
final String json = annotation.toInstantJson();

To create an annotation from an existing Instant annotation JSON, use createAnnotationFromInstantJson() from your document’s AnnotationProvider. For example:

// Deserialize an Instant annotation JSON and create an annotation from it.
val annotation: Annotation = document
    .getAnnotationProvider()
    .createAnnotationFromInstantJson(json)
// Deserialize an Instant annotation JSON and create an annotation from it.
final Annotation annotation = document
    .getAnnotationProvider()
    .createAnnotationFromInstantJson(json);

ℹ️ Note: The created Annotation will automatically be added to the PdfDocument of the used AnnotationProvider during deserialization.

There are some limitations with Instant JSON, in that not all annotation types are currently supported, and only the properties that can be handled correctly across all of PSPDFKit’s supported platforms (iOS, Android, and Web) are serialized. For more information, check out the detailed JSON Format guide.

Here’s an Instant JSON sample payload for an ink annotation:

{
  "v": 1,
  "type": "pspdfkit/ink",
  "bbox": [89, 98, 143, 207],
  "blendMode": "normal",
  "createdAt": "2018-07-03T13:53:03Z",
  "isDrawnNaturally": false,
  "lineWidth": 5,
  "lines": {
    "intensities": [
      [0.5, 0.5, 0.5],
      [0.5, 0.5, 0.5]
    ],
    "points": [
      [
        [92, 101],
        [92, 202],
        [138, 303]
      ],
      [
        [184, 101],
        [184, 202],
        [230, 303]
      ]
    ]
  },
  "opacity": 1,
  "pageIndex": 0,
  "strokeColor": "#AA47BE",
  "updatedAt": "2018-07-03T13:53:03Z"
}

Instant Document JSON API

Document JSON is a serializable representation of the current changes to a document, i.e. a diff between the PdfDocument’s saved and unsaved changes. This can be used to transfer a set of changes across devices without having to send the entire PDF, which could potentially be large. PSPDFKit for Web uses this in standalone deployment to reduce bandwidth usage. Currently, the generated JSON only contains changes to annotations.

To generate Instant JSON for documents, use the static exportDocumentJson() or exportDocumentJsonAsync() of the DocumentJsonFormatter class. Pass in the document from which you wish to retrieve currently unsaved changes in JSON form, as well as an OutputStream that will receive the JSON string. For example:

val outputStream = ByteArrayOutputStream()
DocumentJsonFormatter.exportDocumentJson(document, outputStream)
val jsonString = outputStream.toString()
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DocumentJsonFormatter.exportDocumentJson(document, outputStream);
final String jsonString = outputStream.toString();

⚠️ Warning: Exporting a document to JSON takes some time, which depends upon the device and the number of changes. In order to keep your UI thread responsive, consider using exportDocumentJsonAsync() when starting the JSON export from the main thread.

Applying Document JSON to a Document

An existing Document JSON can be applied to a document by calling importDocumentJson() or importDocumentJsonAsync() of the DocumentJsonFormatter class. Pass in a DataProvider serving the Document JSON data, as well as a PdfDocument instance into which the changes of the JSON should be imported. For example:

// You can use any `DataProvider` instance to serve your JSON data.
val dataProvider = DocumentJsonDataProvider()
DocumentJsonFormatter.importDocumentJson(document, dataProvider)
// You can use any `DataProvider` instance to serve your JSON data.
final DataProvider dataProvider = new DocumentJsonDataProvider();
DocumentJsonFormatter.importDocumentJson(document, dataProvider);

💡 Tip: The DocumentJsonDataProvider is a simple example data provider as implemented in the DataProviderExample of our Catalog app. Our Data Providers guide covers everything you need to know about data providers.

Here’s an Instant JSON sample payload for a document with an ink annotation:

{
  "annotations": [
    {
      "v": 1,
      "type": "pspdfkit/ink",
      "id": "01CHG7QMTDT1JFYQ8BSKGT6F3P",
      "bbox": [97.5, 97.5, 155, 205],
      "blendMode": "normal",
      "createdAt": "2018-07-03T14:11:21Z",
      "creatorName": "John Appleseed",
      "isDrawnNaturally": false,
      "lineWidth": 5,
      "lines": {
        "intensities": [[0.5, 0.5, 0.5], [0.5, 0.5, 0.5]],
        "points": [
          [[100, 100], [100, 200], [150, 300]],
          [[200, 100], [200, 200], [250, 300]]
        ]
      },
      "name": "A167811E-6D10-4546-A147-B7AD775FE8AC",
      "note": "",
      "opacity": 1,
      "pageIndex": 0,
      "strokeColor": "#AA47BE",
      "updatedAt": "2018-07-03T14:11:21Z"
    }
  ],
  "format": "https://pspdfkit.com/instant-json/v1",
  "pdfId": {
    "changing": "wljL9fB/TPOAuGjHAsAsHg==",
    "permanent": "qTwUmg5VSm6ysfzcPlFvnQ=="
  },
  "skippedPdfObjectIds": [557]
}