Repository: FHIR/sushi
Pull request #453 (2020-05-20 14:01:47)

Don't export sparse differentials
Although the IG Publisher *should* handle sparse differentials, it doesn't always handle them well.

Since adding in intermediate elements is not harmful, nor is it wrong, just do it to avoid any bugs in the publisher related to sparse differentials.

cmoesel: Regarding the general comment, I think that is because I add a differential for the slice root of any slices with diffs.  This is actually something I didn't do at first, but added this morning -- as it seemed weird to have an element list like:
* `some`
* `some.path:someslice`
I felt like I wanted it to be:
* `some`
* `some.path`
* `some.path:someslice`
So that's what I did.  But I didn't think about how it might change the rendering (particularly for extensions).

What do you think? Does it make sense to have the root rendered in the differential whenever there are slices?  Or should I remove that bit?  It certainly cleans up the code a little if I remove that logic.
ngfreiter: I could go either way, but I do think it looks better rendered in the IG to *not* have the `extension` rendered, since I don't think that really adds anything to the IG ya know? Perhaps it looks better to have the `extension` present if you are looking at the actual JSON itself.
cmoesel: I think I'd also like to try it for a non-extension slice to see how it looks.  So we'd need to do a profile of a profile (that already slices something) and add a new slice or modify an existing slice -- and see the difference in rendering there.
ngfreiter: Things seem similar for slices, new way:
![image](https://user-images.githubusercontent.com/52747882/82472303-f9cd3000-9a95-11ea-9c2f-3cf1dac572b4.png)
old way: 
![image](https://user-images.githubusercontent.com/52747882/82472349-094c7900-9a96-11ea-9649-bb9c0b0ff9b5.png)

cmoesel: Yeah, OK.  I can remove that bit then.  It's probably not necessary, and I'd rather the rendering look nice than the JSON look nice.
cmoesel: Latest commits address @ngfreiter's comments, including removing the slice root from differentials (except when it is indeed different or has children that are different).

Patch:
diff --git a/src/fhirtypes/ElementDefinition.ts b/src/fhirtypes/ElementDefinition.ts
index f674d2f..65e96f5 100644
--- a/src/fhirtypes/ElementDefinition.ts
+++ b/src/fhirtypes/ElementDefinition.ts
@@ -1901,7 +1901,10 @@ export type ElementDefinitionMapping = {
 /**
  * A barebones and lenient definition of ElementDefinition JSON
  */
-interface LooseElementDefJSON {
+export interface LooseElementDefJSON {
+  id?: string;
+  path?: string;
+  slicing?: ElementDefinitionSlicing;
   type?: ElementDefinitionTypeJSON[];
   binding?: ElementDefinitionBinding;
   // [key: string]: any;
diff --git a/src/fhirtypes/StructureDefinition.ts b/src/fhirtypes/StructureDefinition.ts
index df39e97..31df064 100644
--- a/src/fhirtypes/StructureDefinition.ts
+++ b/src/fhirtypes/StructureDefinition.ts
@@ -1,10 +1,6 @@
 import upperFirst from 'lodash/upperFirst';
 import cloneDeep from 'lodash/cloneDeep';
-import {
-  ElementDefinition,
-  ElementDefinitionType,
-  ElementDefinitionSlicing
-} from './ElementDefinition';
+import { ElementDefinition, ElementDefinitionType, LooseElementDefJSON } from './ElementDefinition';
 import { Meta } from './specialTypes';
 import { Identifier, CodeableConcept, Coding, Narrative, Resource, Extension } from './dataTypes';
 import { ContactDetail, UsageContext } from './metaDataTypes';
@@ -341,37 +337,59 @@ export class StructureDefinition {
       j.snapshot = { element: this.elements.map(e => e.toJSON()) };
     }
 
-    const differentialElements = this.elements
-      .filter(e => e.hasDiff())
-      .map(e => e.calculateDiff().toJSON());
-    const defaultDifferentialElements = [{ id: this.elements[0].id, path: this.elements[0].path }];
-
-    j.differential = {
-      element: differentialElements.length > 0 ? differentialElements : defaultDifferentialElements
+    // Create a cached hasDiff function since hasDiff can be expensive and may be called multiple times
+    // per element when generating the differential.
+    const hasDiffCache: Map<string, boolean> = new Map();
+    const cachedHasDiff = (element: ElementDefinition): boolean => {
+      if (!hasDiffCache.has(element.id)) {
+        hasDiffCache.set(element.id, element.hasDiff());
+      }
+      return hasDiffCache.get(element.id);
     };
 
-    // Post-process the differential to remove any choice[x] elements if the only thing they do is establish the type
-    // slicing.  In this case, the type slicing can be inferred and does not need to be explicitly added.
-    // See: https://blog.fire.ly/2019/09/13/type-slicing-in-fhir-r4/
-    j.differential.element = j.differential.element.filter(
-      (e: { id: string; path: string; slicing: ElementDefinitionSlicing }) => {
-        return (
-          // not a choice, or
-          !e.id.endsWith('[x]') ||
-          // is a choice but not one whose only diff is the standard type slicing
-          !(
-            Object.keys(e).length === 3 &&
-            e.id &&
-            e.path &&
-            e.slicing?.discriminator?.length === 1 &&
-            e.slicing.discriminator[0].type === 'type' &&
-            e.slicing.discriminator[0].path === '$this' &&
-            e.slicing.rules === 'open' &&
-            (e.slicing.ordered == null || e.slicing.ordered === false)
-          )
-        );
+    // Populate the differential (including intermediate elements to avoid a "sparse differential")
+    j.differential = { element: [] };
+    this.elements.forEach(e => {
+      if (cachedHasDiff(e)) {
+        const diff = e.calculateDiff().toJSON();
+        const isTypeSlicingChoiceDiff =
+          diff.id.endsWith('[x]') &&
+          Object.keys(diff).length === 3 &&
+          diff.id &&
+          diff.path &&
+          diff.slicing?.discriminator?.length === 1 &&
+          diff.slicing.discriminator[0].type === 'type' &&
+          diff.slicing.discriminator[0].path === '$this' &&
+          diff.slicing.rules === 'open' &&
+          (diff.slicing.ordered == null || diff.slicing.ordered === false);
+
+        // choice[x] elements that differ only by the standard type slicing definition don't need to go in the
+        // differential, as they are inferred. See: https://blog.fire.ly/2019/09/13/type-slicing-in-fhir-r4/
+        if (!isTypeSlicingChoiceDiff) {
+          j.differential.element.push(diff);
+        } else if (e.children().some(c => cachedHasDiff(c))) {
+          // Even for a type-slicing choice diff, we want at least a simple diff when it has children with a diff
+          j.differential.element.push({
+            id: e.id,
+            path: e.path
+          });
+        }
+      } else if (e.children().some(c => cachedHasDiff(c))) {
+        // Since it has children with a diff, at least add a simple diff element to avoid "sparse differentials"
+        j.differential.element.push({
+          id: e.id,
+          path: e.path
+        });
       }
-    );
+    });
+
+    // Never have an empty differential. It makes the IG Publisher mad.
+    if (j.differential.element.length === 0) {
+      j.differential.element.push({
+        id: this.elements[0].id,
+        path: this.elements[0].path
+      });
+    }
 
     // If the StructureDefinition is in progress, we want to persist that in the JSON so that when
     // the Fisher retrieves it from a package and converts to JSON, the inProgress state will be
@@ -821,8 +839,8 @@ export type PathPart = {
 interface LooseStructDefJSON {
   resourceType: string;
   derivation?: string;
-  snapshot?: { element: any[] };
-  differential?: { element: any[] };
+  snapshot?: { element: LooseElementDefJSON[] };
+  differential?: { element: LooseElementDefJSON[] };
   inProgress?: boolean;
   // [key: string]: any;
 }


Test patch:
diff --git a/test/export/StructureDefinitionExporter.test.ts b/test/export/StructureDefinitionExporter.test.ts
index a2e63c3..c550ce9 100644
--- a/test/export/StructureDefinitionExporter.test.ts
+++ b/test/export/StructureDefinitionExporter.test.ts
@@ -3538,12 +3538,11 @@ describe('StructureDefinitionExporter', () => {
     const sd = pkg.profiles[0];
     const json = sd.toJSON();
 
-    expect(json.differential.element).toHaveLength(1);
-    expect(json.differential.element[0]).toEqual({
-      id: 'Observation.subject',
-      path: 'Observation.subject',
-      min: 1
-    });
+    expect(json.differential.element).toHaveLength(2);
+    expect(json.differential.element).toEqual([
+      { id: 'Observation', path: 'Observation' },
+      { id: 'Observation.subject', path: 'Observation.subject', min: 1 }
+    ]);
   });
 
   it('should correctly generate a diff containing only changed elements when elements are unfolded', () => {
@@ -3568,17 +3567,27 @@ describe('StructureDefinitionExporter', () => {
     const sd = pkg.profiles[0];
     const json = sd.toJSON();
 
-    expect(json.differential.element).toHaveLength(2);
-    expect(json.differential.element[0]).toEqual({
-      id: 'Observation.code.coding',
-      path: 'Observation.code.coding',
-      min: 1
-    });
-    expect(json.differential.element[1]).toEqual({
-      id: 'Observation.code.coding.userSelected',
-      path: 'Observation.code.coding.userSelected',
-      min: 1
-    });
+    expect(json.differential.element).toHaveLength(4);
+    expect(json.differential.element).toEqual([
+      {
+        id: 'Observation',
+        path: 'Observation'
+      },
+      {
+        id: 'Observation.code',
+        path: 'Observation.code'
+      },
+      {
+        id: 'Observation.code.coding',
+        path: 'Observation.code.coding',
+        min: 1
+      },
+      {
+        id: 'Observation.code.coding.userSelected',
+        path: 'Observation.code.coding.userSelected',
+        min: 1
+      }
+    ]);
   });
 
   it('should correctly generate a diff containing only changed elements when elements are sliced', () => {
@@ -3630,53 +3639,58 @@ describe('StructureDefinitionExporter', () => {
     const sd = pkg.profiles[0];
     const json = sd.toJSON();
 
-    const diffs = json.differential.element;
-    expect(diffs).toHaveLength(7);
-    expect(diffs[0]).toEqual({
-      id: 'Observation.component',
-      path: 'Observation.component',
-      slicing: {
-        discriminator: [{ type: 'pattern', path: 'code' }],
-        rules: 'open'
+    expect(json.differential.element).toHaveLength(8);
+    expect(json.differential.element).toEqual([
+      {
+        id: 'Observation',
+        path: 'Observation'
       },
-      comment: 'BP comment'
-    });
-    expect(diffs[1]).toEqual({
-      id: 'Observation.component:SystolicBP',
-      path: 'Observation.component',
-      sliceName: 'SystolicBP',
-      max: '1'
-    });
-    expect(diffs[2]).toEqual({
-      id: 'Observation.component:SystolicBP.code',
-      path: 'Observation.component.code',
-      patternCodeableConcept: {
-        coding: [{ code: '8480-6', system: 'http://loinc.org' }]
-      }
-    });
-    expect(diffs[3]).toEqual({
-      id: 'Observation.component:SystolicBP.value[x]',
-      path: 'Observation.component.value[x]',
-      type: [{ code: 'Quantity' }]
-    });
-    expect(diffs[4]).toEqual({
-      id: 'Observation.component:DiastolicBP',
-      path: 'Observation.component',
-      sliceName: 'DiastolicBP',
-      max: '1'
-    });
-    expect(diffs[5]).toEqual({
-      id: 'Observation.component:DiastolicBP.code',
-      path: 'Observation.component.code',
-      patternCodeableConcept: {
-        coding: [{ code: '8462-4', system: 'http://loinc.org' }]
+      {
+        id: 'Observation.component',
+        path: 'Observation.component',
+        slicing: {
+          discriminator: [{ type: 'pattern', path: 'code' }],
+          rules: 'open'
+        },
+        comment: 'BP comment'
+      },
+      {
+        id: 'Observation.component:SystolicBP',
+        path: 'Observation.component',
+        sliceName: 'SystolicBP',
+        max: '1'
+      },
+      {
+        id: 'Observation.component:SystolicBP.code',
+        path: 'Observation.component.code',
+        patternCodeableConcept: {
+          coding: [{ code: '8480-6', system: 'http://loinc.org' }]
+        }
+      },
+      {
+        id: 'Observation.component:SystolicBP.value[x]',
+        path: 'Observation.component.value[x]',
+        type: [{ code: 'Quantity' }]
+      },
+      {
+        id: 'Observation.component:DiastolicBP',
+        path: 'Observation.component',
+        sliceName: 'DiastolicBP',
+        max: '1'
+      },
+      {
+        id: 'Observation.component:DiastolicBP.code',
+        path: 'Observation.component.code',
+        patternCodeableConcept: {
+          coding: [{ code: '8462-4', system: 'http://loinc.org' }]
+        }
+      },
+      {
+        id: 'Observation.component:DiastolicBP.value[x]',
+        path: 'Observation.component.value[x]',
+        type: [{ code: 'Quantity' }]
       }
-    });
-    expect(diffs[6]).toEqual({
-      id: 'Observation.component:DiastolicBP.value[x]',
-      path: 'Observation.component.value[x]',
-      type: [{ code: 'Quantity' }]
-    });
+    ]);
   });
 
   it('should include sliceName in a differential when an attribute of the slice is changed', () => {
@@ -3688,13 +3702,23 @@ describe('StructureDefinitionExporter', () => {
     exporter.exportStructDef(profile);
     const sd = pkg.profiles[0];
     const json = sd.toJSON();
-    expect(json.differential.element).toHaveLength(1);
-    expect(json.differential.element[0]).toEqual({
-      id: 'Observation.code.coding:RespRateCode',
-      path: 'Observation.code.coding',
-      sliceName: 'RespRateCode',
-      mustSupport: true
-    });
+    expect(json.differential.element).toHaveLength(3);
+    expect(json.differential.element).toEqual([
+      {
+        id: 'Observation',
+        path: 'Observation'
+      },
+      {
+        id: 'Observation.code',
+        path: 'Observation.code'
+      },
+      {
+        id: 'Observation.code.coding:RespRateCode',
+        path: 'Observation.code.coding',
+        sliceName: 'RespRateCode',
+        mustSupport: true
+      }
+    ]);
   });
 
   it.skip('should not change sliceName based on a CaretValueRule', () => {
diff --git a/test/fhirtypes/StructureDefinition.test.ts b/test/fhirtypes/StructureDefinition.test.ts
index 235e8f0..4040fd3 100644
--- a/test/fhirtypes/StructureDefinition.test.ts
+++ b/test/fhirtypes/StructureDefinition.test.ts
@@ -122,13 +122,17 @@ describe('StructureDefinition', () => {
       valueX.min = 1;
 
       const json = observation.toJSON();
-      expect(json.differential.element).toHaveLength(2);
+      expect(json.differential.element).toHaveLength(3);
       expect(json.differential.element[0]).toEqual({
+        id: 'Observation',
+        path: 'Observation'
+      });
+      expect(json.differential.element[1]).toEqual({
         id: 'Observation.code',
         path: 'Observation.code',
         short: 'Special observation code'
       });
-      expect(json.differential.element[1]).toEqual({
+      expect(json.differential.element[2]).toEqual({
         id: 'Observation.value[x]',
         path: 'Observation.value[x]',
         min: 1
@@ -142,13 +146,17 @@ describe('StructureDefinition', () => {
       valueX.min = 1;
 
       const json = observation.toJSON(false);
-      expect(json.differential.element).toHaveLength(2);
+      expect(json.differential.element).toHaveLength(3);
       expect(json.differential.element[0]).toEqual({
+        id: 'Observation',
+        path: 'Observation'
+      });
+      expect(json.differential.element[1]).toEqual({
         id: 'Observation.code',
         path: 'Observation.code',
         short: 'Special observation code'
       });
-      expect(json.differential.element[1]).toEqual({
+      expect(json.differential.element[2]).toEqual({
         id: 'Observation.value[x]',
         path: 'Observation.value[x]',
         min: 1
@@ -156,6 +164,89 @@ describe('StructureDefinition', () => {
       expect(json.snapshot).toBeUndefined();
     });
 
+    it('should contain intermediate elements in differential to avoid sparse differentials', () => {
+      const componentCode = observation.elements.find(e => e.id === 'Observation.component.code');
+      componentCode.short = 'Special component code';
+
+      const json = observation.toJSON();
+      expect(json.differential.element).toHaveLength(3);
+      expect(json.differential.element[0]).toEqual({
+        id: 'Observation',
+        path: 'Observation'
+      });
+      expect(json.differential.element[1]).toEqual({
+        id: 'Observation.component',
+        path: 'Observation.component'
+      });
+      expect(json.differential.element[2]).toEqual({
+        id: 'Observation.component.code',
+        path: 'Observation.component.code',
+        short: 'Special component code'
+      });
+    });
+
+    it('should contain intermediate elements in differential to avoid sparse differentials without generating snapshot', () => {
+      const componentCode = observation.elements.find(e => e.id === 'Observation.component.code');
+      componentCode.short = 'Special component code';
+
+      const json = observation.toJSON(false);
+      expect(json.differential.element).toHaveLength(3);
+      expect(json.differential.element[0]).toEqual({
+        id: 'Observation',
+        path: 'Observation'
+      });
+      expect(json.differential.element[1]).toEqual({
+        id: 'Observation.component',
+        path: 'Observation.component'
+      });
+      expect(json.differential.element[2]).toEqual({
+        id: 'Observation.component.code',
+        path: 'Observation.component.code',
+        short: 'Special component code'
+      });
+    });
+
+    it('should contain intermediate elements in differential for children of choices', () => {
+      // If we have this:
+      //   * valueQuantity ^short = "value[x] Quantity choice"
+      // then value[x] should not appear because IG Publisher infers it.
+      // But if we have this:
+      //   * value[x].extension ^short = "Extension on value[x]"
+      //   * valueQuantity ^short = "the quantity choice"
+      // then it should appear since it has a direct child.
+      const valueX = observation.elements.find(e => e.id === 'Observation.value[x]');
+      valueX.sliceIt('type', '$this', false, 'open');
+      valueX.unfoldChoiceElementTypes(fisher);
+      const valueXExtension = observation.elements.find(
+        e => e.id === 'Observation.value[x].extension'
+      );
+      valueXExtension.short = 'Extension on value[x]';
+      const valueQuantity = valueX.addSlice('valueQuantity', new ElementDefinitionType('Quantity'));
+      valueQuantity.short = 'the quantity choice';
+
+      const json = observation.toJSON();
+      expect(json.differential.element).toHaveLength(4);
+      expect(json.differential.element[0]).toEqual({
+        id: 'Observation',
+        path: 'Observation'
+      });
+      expect(json.differential.element[1]).toEqual({
+        id: 'Observation.value[x]',
+        path: 'Observation.value[x]'
+      });
+      expect(json.differential.element[2]).toEqual({
+        id: 'Observation.value[x].extension',
+        path: 'Observation.value[x].extension',
+        short: 'Extension on value[x]'
+      });
+      expect(json.differential.element[3]).toEqual({
+        id: 'Observation.valueQuantity',
+        path: 'Observation.valueQuantity',
+        short: 'the quantity choice',
+        type: [{ code: 'Quantity' }]
+      });
+    });
+
     it('should reflect basic differential for structure definitions with no changes', () => {
       const json = observation.toJSON();
       expect(json.differential).toEqual({ element: [{ id: 'Observation', path: 'Observation' }] });
@@ -203,8 +294,13 @@ describe('StructureDefinition', () => {
       expect(valueStringSnapshot.sliceName).toEqual('valueString');
       expect(valueStringSnapshot.short).toBe('the string choice');
       // then check that differential has value[x] and shortcut syntax valueQuantity and valueString
-      expect(json.differential.element).toHaveLength(3);
-      const valueXDiff = json.differential.element[0];
+      expect(json.differential.element).toHaveLength(4);
+      const rootDiff = json.differential.element[0];
+      expect(rootDiff).toEqual({
+        id: 'Observation',
+        path: 'Observation'
+      });
+      const valueXDiff = json.differential.element[1];
       expect(valueXDiff).toEqual({
         id: 'Observation.value[x]',
         path: 'Observation.value[x]',
@@ -215,13 +311,13 @@ describe('StructureDefinition', () => {
           rules: 'open'
         }
       });
-      const valueQuantityDiff = json.differential.element[1];
+      const valueQuantityDiff = json.differential.element[2];
       expect(valueQuantityDiff.id).toBe('Observation.valueQuantity');
       expect(valueQuantityDiff.path).toBe('Observation.valueQuantity');
       expect(valueQuantityDiff.type).toEqual([{ code: 'Quantity' }]);
       expect(valueQuantityDiff.sliceName).toBeUndefined();
       expect(valueQuantitySnapshot.short).toBe('the quantity choice');
-      const valueStringDiff = json.differential.element[2];
+      const valueStringDiff = json.differential.element[3];
       expect(valueStringDiff.id).toBe('Observation.valueString');
       expect(valueStringDiff.path).toBe('Observation.valueString');
       expect(valueStringDiff.type).toEqual([{ code: 'string' }]);
@@ -262,14 +358,19 @@ describe('StructureDefinition', () => {
       expect(valueStringSnapshot.sliceName).toEqual('valueString');
       expect(valueStringSnapshot.short).toBe('the string choice');
       // then check that differential does NOT have value[x] but has shortcut syntax for valueQuantity and valueString
-      expect(json.differential.element).toHaveLength(2);
-      const valueQuantityDiff = json.differential.element[0];
+      expect(json.differential.element).toHaveLength(3);
+      const rootDiff = json.differential.element[0];
+      expect(rootDiff).toEqual({
+        id: 'Observation',
+        path: 'Observation'
+      });
+      const valueQuantityDiff = json.differential.element[1];
       expect(valueQuantityDiff.id).toBe('Observation.valueQuantity');
       expect(valueQuantityDiff.path).toBe('Observation.valueQuantity');
       expect(valueQuantityDiff.type).toEqual([{ code: 'Quantity' }]);
       expect(valueQuantityDiff.sliceName).toBeUndefined();
       expect(valueQuantitySnapshot.short).toBe('the quantity choice');
-      const valueStringDiff = json.differential.element[1];
+      const valueStringDiff = json.differential.element[2];
       expect(valueStringDiff.id).toBe('Observation.valueString');
       expect(valueStringDiff.path).toBe('Observation.valueString');
       expect(valueStringDiff.type).toEqual([{ code: 'string' }]);
@@ -314,8 +415,13 @@ describe('StructureDefinition', () => {
       expect(valueStringSnapshot.sliceName).toEqual('valueString');
       expect(valueStringSnapshot.short).toBe('the string choice');
       // then check that differential does NOT have value[x] but has shortcut syntax for valueQuantity and valueString
-      expect(json.differential.element).toHaveLength(3);
-      const valueXDiff = json.differential.element[0];
+      expect(json.differential.element).toHaveLength(4);
+      const rootDiff = json.differential.element[0];
+      expect(rootDiff).toEqual({
+        id: 'Observation',
+        path: 'Observation'
+      });
+      const valueXDiff = json.differential.element[1];
       expect(valueXDiff).toEqual({
         id: 'Observation.value[x]',
         path: 'Observation.value[x]',
@@ -326,13 +432,13 @@ describe('StructureDefinition', () => {
         },
         short: 'a choice of many things'
       });
-      const valueQuantityDiff = json.differential.element[1];
+      const valueQuantityDiff = json.differential.element[2];
       expect(valueQuantityDiff.id).toBe('Observation.valueQuantity');
       expect(valueQuantityDiff.path).toBe('Observation.valueQuantity');
       expect(valueQuantityDiff.type).toEqual([{ code: 'Quantity' }]);
       expect(valueQuantityDiff.sliceName).toBeUndefined();
       expect(valueQuantitySnapshot.short).toBe('the quantity choice');
-      const valueStringDiff = json.differential.element[2];
+      const valueStringDiff = json.differential.element[3];
       expect(valueStringDiff.id).toBe('Observation.valueString');
       expect(valueStringDiff.path).toBe('Observation.valueString');
       expect(valueStringDiff.type).toEqual([{ code: 'string' }]);
@@ -375,8 +481,8 @@ describe('StructureDefinition', () => {
       expect(catCoding).toEqual(catCodingOriginal);
       expect(cat).toEqual(catOriginal);
 
-      const vsCatCodingDiff = json.differential.element[3];
-      const vsCatDiff = json.differential.element[2];
+      const vsCatCodingDiff = json.differential.element[4];
+      const vsCatDiff = json.differential.element[3];
       expect(vsCatCodingDiff).toEqual({
         id: 'Observation.category:VSCat.coding',
         path: 'Observation.category.coding',
@@ -388,8 +494,8 @@ describe('StructureDefinition', () => {
         sliceName: 'VSCat'
       });
 
-      const catCodingDiff = json.differential.element[1];
-      const catDiff = json.differential.element[0];
+      const catCodingDiff = json.differential.element[2];
+      const catDiff = json.differential.element[1];
       expect(catCodingDiff).toEqual({
         id: 'Observation.category.coding',
         path: 'Observation.category.coding',
@@ -399,6 +505,12 @@ describe('StructureDefinition', () => {
         id: 'Observation.category',
         path: 'Observation.category'
       });
+
+      const rootDiff = json.differential.element[0];
+      expect(rootDiff).toEqual({
+        id: 'Observation',
+        path: 'Observation'
+      });
     });
   });