Instant JSON

Instant JSON is our approach to bringing annotations into a sane format while keeping all important properties to make the Instant JSON spec work with PDF. It is 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 this 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 will 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 does not contain an annotation with the same pdfObjectId, it will be interpreted as a deleted annotation. An annotation that is 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 already 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:

Copy
1
2
3
4
5
6
7
8
9
10
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 and a changing PDF ID. According to the PDF spec, a PDF document must contain those 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 should not be set.

  • annotations

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

  • formFieldValues

    This is a list of modified form field values. Objects follow the Instant Form Field Value JSON format. This list will not be preset when no form field values have been modified.

Exporting Instant JSON

Copy
1
2
3
instance.exportInstantJSON().then(instantJSON => {
  console.log(instantJSON); // => { format: "https://pspdfkit.com/instant-json/v1", ...}
});
Copy
1
2
3
instance.exportInstantJSON().then(function (instantJSON) {
  console.log(instantJSON); // => { format: "https://pspdfkit.com/instant-json/v1", ...}
});

Instant JSON works seamlessly with the annotation API. If you want to persist annotations whenever changes are made, we recommend that you use the annotations.didSave event. This event will be triggered automatically and can be configured via Configuration#autoSaveMode.

Note for standalone users: Instead of saving annotations to the backend, a save operation will persist the annotations in memory until they are either exported via Instance#exportInstantJSON or written to the PDF document when you export it using Instance#exportPDF. Unsaved annotations will not be exported to reflect the server behavior:

Copy
1
2
3
4
5
6
7
8
9
10
instance.addEventListener('annotations.didSave', async () => {
  const instantJSON = await instance.exportInstantJSON();
  await fetch("https://your-server.com/instant-json", {
    method: "post",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(instantJSON)
  });
});
Copy
1
2
3
4
5
6
7
8
9
10
instance.addEventListener('annotations.didSave', function () {
  instance.exportInstantJSON().then(function (instantJSON) {
      var req = new XMLHttpRequest();
      req.open("GET", "https://your-server.com/instant-json");
      req.setRequestHeader("Content-Type", "application/json");
      req.send(JSON.stringify(instantJSON));
      req.send();
    });
  });
});

Here's an Instant JSON sample payload for an Ink Annotation:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
{
  "bbox": [
    89.58633422851562,
    98.5791015625,
    143.12948608398438,
    207.1583251953125
  ],
  "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.08633422851562,
          101.07916259765625
        ],
        [
          92.08633422851562,
          202.15826416015625
        ],
        [
          138.12950134277344,
          303.2374267578125
        ]
      ],
      [
        [
          184.17266845703125,
          101.07916259765625
        ],
        [
          184.17266845703125,
          202.15826416015625
        ],
        [
          230.2158203125,
          303.2374267578125
        ]
      ]
    ]
  },
  "opacity": 1,
  "pageIndex": 0,
  "strokeColor": "#AA47BE",
  "type": "pspdfkit/ink",
  "updatedAt": "2018-07-03T13:53:03Z",
  "v": 1
}

Importing Instant JSON

Standalone Importing

When opening a document with Instant JSON using our standalone setup, make sure you always use the same unmodified PDF that was used to create that Instant JSON:

Copy
1
2
3
4
5
6
7
8
9
PSPDFKit.load({
  pdf: "https://your-server.com/some/document.pdf",
  instantJSON: {
    format: "https://pspdfkit.com/instant-json/v1",
    annotations: [
      { id: "01BS964AM5Z01J9MKBK64F22BQ", type: "pspdfkit/text", ...}
    ],
  },
})
Copy
1
2
3
4
5
6
7
8
9
PSPDFKit.load({
  pdf: "https://your-server.com/some/document.pdf",
  instantJSON: {
    format: "https://pspdfkit.com/instant-json/v1",
    annotations: [
      { id: "01BS964AM5Z01J9MKBK64F22BQ", type: "pspdfkit/text", ...}
    ],
  },
})

Server Importing

When you upload a new document to PSPDFKit Server, you can include an Instant JSON document as a second file in the upload. Follow the server guides for an in-depth example.

Here's an Instant JSON sample payload for a document with an Ink Annotation:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
{
  "annotations": [
    {
      "bbox": [
        97.5,
        97.5,
        155,
        205
      ],
      "blendMode": "normal",
      "createdAt": "2018-07-03T14:11:21Z",
      "creatorName": "John Appleseed",
      "id": "01CHG7QMTDT1JFYQ8BSKGT6F3P",
      "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",
      "type": "pspdfkit/ink",
      "updatedAt": "2018-07-03T14:11:21Z",
      "v": 1
    }
  ],
  "format": "https://pspdfkit.com/instant-json/v1",
  "pdfId": {
    "changing": "wljL9fB/TPOAuGjHAsAsHg==",
    "permanent": "qTwUmg5VSm6ysfzcPlFvnQ=="
  },
  "skippedPdfObjectIds": [
    557
  ]
}