diff --git a/demo/apis.json b/demo/apis.json
index d134377..b75262e 100644
--- a/demo/apis.json
+++ b/demo/apis.json
@@ -29,5 +29,6 @@
"shopper-products/shopper-products.yaml": { "type": "OAS 3.0", "mime": "application/yaml" },
"W-12142859/W-12142859.yaml": "OAS 2.0",
"nulleable/nulleable.yaml": "OAS 3.0",
- "nullable-test/nullable-test.yaml": { "type": "OAS 3.0", "mime": "application/yaml" }
+ "nullable-test/nullable-test.yaml": { "type": "OAS 3.0", "mime": "application/yaml" },
+ "nested-examples/nested-examples-oas3.json": { "type": "OAS 3.0", "mime": "application/json" }
}
diff --git a/demo/index.js b/demo/index.js
index 94d02a8..cb8aee2 100644
--- a/demo/index.js
+++ b/demo/index.js
@@ -99,6 +99,7 @@ class ApiDemo extends ApiDemoPage {
_apiListTemplate() {
return [
+ ['nested-examples-oas3', 'Nested Examples'],
['nullable-test', 'Nullable Test (Comprehensive)'],
['nulleable', 'Nulleable test'],
['grpc-test', 'GRPC test'],
diff --git a/demo/nested-examples/nested-examples-oas3.json b/demo/nested-examples/nested-examples-oas3.json
new file mode 100644
index 0000000..a58e1de
--- /dev/null
+++ b/demo/nested-examples/nested-examples-oas3.json
@@ -0,0 +1,472 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "version": "1.0.0",
+ "title": "netsted-examples-oas3"
+ },
+ "paths": {
+ "/productOrderItems": {
+ "get": {
+ "summary": "List product order items",
+ "operationId": "listProductOrderItems",
+ "responses": {
+ "200": {
+ "description": "List of product order items",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ProductOrderItem"
+ }
+ },
+ "examples": {
+ "default": {
+ "summary": "Full productOrderItem example list (300+ lines)",
+ "value": [
+ {
+ "state": "acknowledged",
+ "@type": "productOrderItem",
+ "productOrderItemRelationship": [
+ {
+ "id": "2",
+ "relationshipType": "isParent",
+ "@type": "OrderItemRelationship"
+ }
+ ],
+ "product": {
+ "@type": "product",
+ "productCharacteristic": [
+ {
+ "valueType": "string",
+ "value": "80/20",
+ "name": "productSpeed",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "Restart",
+ "name": "provisioningCommand",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "Standard",
+ "name": "careLevel",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "Managed Standard",
+ "name": "installationType",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "Jane-Kelly",
+ "name": "installationContactNamePrimary",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "07449929291",
+ "name": "installationContactNumberPrimary",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "Jane",
+ "name": "installationContactNameSecondary",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "07449929292",
+ "name": "installationContactNumberSecondary",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "cgiuk@tt.com",
+ "name": "installationContactEmail",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "HD05091996",
+ "name": "partnerOrderReference",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "Static IP - 4",
+ "name": "ipBlockSize",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "abclub.net",
+ "name": "domainName",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "CCF",
+ "name": "retailerId",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "test test",
+ "name": "otherUses",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "valueType": "string",
+ "value": "test test",
+ "name": "otherDescription",
+ "@type": "StringCharacteristic"
+ }
+ ],
+ "place": [
+ {
+ "@type": "place",
+ "role": "installationAddress",
+ "place": {
+ "postcode": "M5 3BL",
+ "@type": "PXCGeographicSubAddressUnit",
+ "externalId": [
+ {
+ "@type": "ExternalIdentifier",
+ "externalIdentifierType": "galk",
+ "id": "A90000791299"
+ },
+ {
+ "@type": "ExternalIdentifier",
+ "externalIdentifierType": "districtCode",
+ "id": "LV"
+ }
+ ]
+ }
+ }
+ ],
+ "name": "C-OR-SOGEA"
+ },
+ "appointment": {
+ "date": "2024-09-30",
+ "timeSlot": "AM",
+ "id": "48160",
+ "@type": "appointment"
+ },
+ "action": "add",
+ "id": "0001"
+ },
+ {
+ "appointment": {
+ "date": "2024-09-30",
+ "timeSlot": "AM",
+ "id": "50795",
+ "@type": "appointment"
+ },
+ "state": "acknowledged",
+ "@type": "productOrderItem",
+ "productOrderItemRelationship": [
+ {
+ "id": "1",
+ "relationshipType": "isChild",
+ "@type": "OrderItemRelationship"
+ }
+ ],
+ "product": {
+ "relatedParty": [
+ {
+ "title": "Mr",
+ "otherName": "John",
+ "familyName": "Smith",
+ "role": "edb",
+ "@type": "IndividualWithAddress",
+ "place": {
+ "buildingName": "PX Apartments",
+ "subUnitNumber": "FLAT3",
+ "streetNr": "150",
+ "streetName": "Warburton Street",
+ "subStreetName": "behind Queen Vic pub",
+ "locality": "Manchester",
+ "dependentLocality": "Salford",
+ "doubleDependentLocality": "Ordsall",
+ "postcode": "M5 3BL",
+ "@type": "PXCGeographicSubAddressUnit"
+ }
+ },
+ {
+ "name": "Alphabeta",
+ "otherName": "PlatformX Communications",
+ "nameType": "Ltd",
+ "role": "dq",
+ "@type": "OrganizationWithAddress",
+ "place": {
+ "buildingName": "Alphabeta",
+ "subUnitNumber": "FLAT6",
+ "streetNr": "14-18",
+ "streetName": "Finsbury Square",
+ "subStreetName": "",
+ "locality": "London",
+ "dependentLocality": "Hackney",
+ "doubleDependentLocality": "Shoreditch",
+ "postcode": "EC2A 1BR",
+ "@type": "PXCGeographicSubAddressUnit"
+ },
+ "partyCharacteristic": [
+ {
+ "name": "directoryEntryLineUse",
+ "value": "NormalUse",
+ "valueType": "string",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "name": "directoryEntryPreference",
+ "value": "Normal",
+ "valueType": "string",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "name": "directoryEntryType",
+ "value": "NewDQ",
+ "valueType": "string",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "name": "directoryEntryFilePlacement",
+ "value": "Residential",
+ "valueType": "string",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "name": "directoryEntryPartialAddressIndicator",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ }
+ ]
+ }
+ ],
+ "productCharacteristic": [
+ {
+ "name": "requestedTelephoneNumber",
+ "value": "01123456789",
+ "valueType": "string",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "name": "lcpCupid",
+ "value": "",
+ "valueType": "string",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "name": "oldPostCode",
+ "value": "",
+ "valueType": "String",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "name": "newNumberOverrideAllowed",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "anonymousCallRejection",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "answerService1571RemoteAccess",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "answerService1571",
+ "value": "Basic",
+ "valueType": "string",
+ "@type": "StringCharacteristic"
+ },
+ {
+ "name": "callerDisplay",
+ "value": true,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "chooseToRefuse",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "cliPresentationRestrictionPermanent",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "cliRetrieval",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "subscriberCallForward",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "subscriberCallForwardRemoteAccess",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "countrySpecificBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "directoryEnquiriesBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "highRiskInternationalBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "internationalBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "mobileBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "nationalBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "premiumRateLowBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "premiumRateHighBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "premiumRateAdultBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "servicesLowBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "servicesHighBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "servicesFreeBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "specialServicesBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ },
+ {
+ "name": "subscriberOutgoingCallBar",
+ "value": false,
+ "valueType": "boolean",
+ "@type": "BooleanCharacteristic"
+ }
+ ],
+ "productOffering": {
+ "name": "Pay as you go",
+ "id": "PAYG",
+ "@type": "productOffering"
+ },
+ "name": "C-VOIP",
+ "@type": "product"
+ },
+ "action": "add",
+ "id": "0001"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ProductOrderItem": {
+ "type": "object",
+ "description": "An identified part of the order (product order item).",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "action": {
+ "type": "string"
+ },
+ "state": {
+ "type": "string"
+ },
+ "@type": {
+ "type": "string"
+ },
+ "product": {
+ "type": "object"
+ },
+ "productOrderItemRelationship": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ },
+ "appointment": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 7e4915f..0316d6a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@api-components/api-type-document",
- "version": "4.2.38",
+ "version": "4.2.39",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@api-components/api-type-document",
- "version": "4.2.38",
+ "version": "4.2.39",
"license": "Apache-2.0",
"dependencies": {
"@advanced-rest-client/arc-marked": "^1.1.0",
diff --git a/package.json b/package.json
index bf821e1..33ac7ca 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@api-components/api-type-document",
"description": "A documentation table for type (resource) properties. Works with AMF data model",
- "version": "4.2.38",
+ "version": "4.2.39",
"license": "Apache-2.0",
"main": "index.js",
"module": "index.js",
diff --git a/src/ApiTypeDocument.js b/src/ApiTypeDocument.js
index d26732b..e27ca7f 100644
--- a/src/ApiTypeDocument.js
+++ b/src/ApiTypeDocument.js
@@ -391,6 +391,7 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
this._showExamples = !this.noMainExample && (
this.renderMediaSelector ||
this.isObject ||
+ this.isArray ||
this._renderMainExample
);
}
@@ -530,15 +531,16 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
}
// Determine if we should show the examples section
- // Priority: noMainExample (hide) > renderMediaSelector (show) > isObject (show) > _renderMainExample
+ // Priority: noMainExample (hide) > renderMediaSelector (show) > isObject (show) > isArray (show) > _renderMainExample
this._showExamples = !this.noMainExample && (
this.renderMediaSelector || // Need to show the section for the media type selector
isObject || // Objects can generate examples automatically
+ isArray || // Arrays can generate examples automatically
this._renderMainExample // Has explicit examples
);
- // Effective media type - use 'application/json' as default for objects without mediaType
- this._exampleMediaType = this.mediaType || (isObject ? 'application/json' : undefined);
+ // Effective media type - use 'application/json' as default for objects and arrays without mediaType
+ this._exampleMediaType = this.mediaType || (isObject || isArray ? 'application/json' : undefined);
}
/**
@@ -1141,7 +1143,7 @@ export class ApiTypeDocument extends PropertyDocumentMixin(LitElement) {
: this._renderMainExample;
const exampleMediaType = this._exampleMediaType !== undefined
? this._exampleMediaType
- : (this.mediaType || (this.isObject ? 'application/json' : undefined));
+ : (this.mediaType || (this.isObject || this.isArray ? 'application/json' : undefined));
return html`
${shouldRenderExamples ? html`
diff --git a/test/nested-array-examples.test.js b/test/nested-array-examples.test.js
new file mode 100644
index 0000000..415b7b6
--- /dev/null
+++ b/test/nested-array-examples.test.js
@@ -0,0 +1,200 @@
+/* eslint-disable prefer-destructuring */
+import { fixture, assert, aTimeout, nextFrame } from '@open-wc/testing'
+import { AmfLoader } from './amf-loader.js';
+import '../api-type-document.js';
+
+/** @typedef {import('..').ApiTypeDocument} ApiTypeDocument */
+/** @typedef {import('@api-components/amf-helper-mixin').AmfDocument} AmfDocument */
+
+/**
+ * Test for array response examples in OAS 3.0.
+ *
+ * This test reproduces the bug where example payloads were not displaying
+ * on the Exchange main page for APIs using OpenAPI Specification (OAS) 3.0.0
+ * when the response schema is an array type.
+ *
+ * The issue affected partner automation for order placement and was caused by
+ * the component not recognizing array types as eligible for example rendering,
+ * only checking for object types.
+ *
+ * Bug details:
+ * - Started: January 30, 2026
+ * - Impact: All OAS 3.0.0 specifications with array response schemas
+ * - Examples visible in Design Center but failed to render in Exchange
+ * - RAML specifications were unaffected
+ *
+ * Fix: Added isArray checks alongside isObject checks in:
+ * - _showExamples computation
+ * - _exampleMediaType defaulting (both should default to 'application/json')
+ */
+describe('Array Examples - OAS 3.0 (nested-examples)', () => {
+ const file = 'nested-examples-oas3';
+
+ /**
+ * @returns {Promise}
+ */
+ async function basicFixture() {
+ return fixture(``);
+ }
+
+ /**
+ * Get response payload schema for a specific endpoint and operation
+ * @param {AmfDocument} model
+ * @param {string} endpoint
+ * @param {string} operation
+ * @returns {any}
+ */
+ function getResponsePayloadSchema(model, endpoint, operation) {
+ const op = AmfLoader.lookupOperation(model, endpoint, operation);
+
+ // Find the response with status 200
+ const returnsKey = Object.keys(op).find(k => k.includes('returns'));
+ if (!returnsKey) return null;
+
+ const responses = op[returnsKey];
+ let response200;
+
+ for (const resp of responses) {
+ const statusKey = Object.keys(resp).find(k => k.includes('statusCode'));
+ if (statusKey && resp[statusKey]) {
+ const statusValue = Array.isArray(resp[statusKey]) ? resp[statusKey][0] : resp[statusKey];
+ const status = typeof statusValue === 'object' ? statusValue['@value'] : statusValue;
+ if (status === '200') {
+ response200 = resp;
+ break;
+ }
+ }
+ }
+
+ if (!response200) return null;
+
+ // Get payload
+ const payloadKey = Object.keys(response200).find(k => k.includes('payload'));
+ if (!payloadKey) return null;
+
+ const payloads = response200[payloadKey];
+ const payload = Array.isArray(payloads) ? payloads[0] : payloads;
+
+ // Get schema
+ const schemaKey = Object.keys(payload).find(k => k.includes('schema'));
+ if (!schemaKey) return null;
+
+ const schemas = payload[schemaKey];
+ return Array.isArray(schemas) ? schemas[0] : schemas;
+ }
+
+ [
+ ['Regular model', false],
+ ['Compact model', true],
+ ].forEach((item) => {
+ describe(String(item[0]), () => {
+ let element = /** @type ApiTypeDocument */ (null);
+ let amf;
+
+ before(async () => {
+ amf = await AmfLoader.load(item[1], file);
+ });
+
+ beforeEach(async () => {
+ element = await basicFixture();
+ });
+
+ it('loads the nested-examples-oas3 model', async () => {
+ assert.exists(amf, 'AMF model should be loaded');
+ });
+
+ it('identifies array response schema correctly', async () => {
+ const responseSchema = getResponsePayloadSchema(amf, '/productOrderItems', 'get');
+ assert.exists(responseSchema, 'Response schema should exist');
+
+ element.amf = amf;
+ element.type = responseSchema;
+ await aTimeout(0);
+
+ // Verify it's recognized as an array type
+ assert.isTrue(element.isArray, 'Response schema should be identified as array');
+ });
+
+ it('renders examples section for array responses', async () => {
+ const responseSchema = getResponsePayloadSchema(amf, '/productOrderItems', 'get');
+
+ element.amf = amf;
+ element.type = responseSchema;
+ await aTimeout(0);
+
+ // Verify the examples section is rendered
+ const examplesSection = element.shadowRoot.querySelector('.examples');
+ assert.exists(examplesSection, 'Examples section should be rendered for array responses');
+ });
+
+ it('passes resolved type to api-resource-example-document for arrays', async () => {
+ const responseSchema = getResponsePayloadSchema(amf, '/productOrderItems', 'get');
+
+ element.amf = amf;
+ element.type = responseSchema;
+ await aTimeout(0);
+
+ const exampleDoc = element.shadowRoot.querySelector('api-resource-example-document');
+ assert.exists(exampleDoc, 'api-resource-example-document should be rendered');
+ assert.isDefined(exampleDoc.examples, 'Examples should be passed to the component');
+ });
+
+ it('uses default mediaType for arrays without explicit mediaType', async () => {
+ const responseSchema = getResponsePayloadSchema(amf, '/productOrderItems', 'get');
+
+ element.amf = amf;
+ element.type = responseSchema;
+ // Don't set mediaType explicitly
+ await aTimeout(0);
+
+ const exampleDoc = element.shadowRoot.querySelector('api-resource-example-document');
+ assert.exists(exampleDoc, 'api-resource-example-document should exist');
+ assert.equal(
+ exampleDoc.mediaType,
+ 'application/json',
+ 'Should default to application/json for arrays'
+ );
+ });
+
+ it('sets _showExamples to true for array types', async () => {
+ const responseSchema = getResponsePayloadSchema(amf, '/productOrderItems', 'get');
+
+ element.amf = amf;
+ element.type = responseSchema;
+ await aTimeout(0);
+
+ assert.isTrue(element._showExamples, '_showExamples should be true for arrays');
+ });
+
+ it('respects noMainExample flag for arrays', async () => {
+ const responseSchema = getResponsePayloadSchema(amf, '/productOrderItems', 'get');
+
+ element.amf = amf;
+ element.noMainExample = true;
+ element.type = responseSchema;
+ await element.updateComplete;
+ await nextFrame();
+
+ const examplesSection = element.shadowRoot.querySelector('.examples');
+ assert.notExists(examplesSection, 'Examples should not render when noMainExample is true');
+ });
+
+ it('renders complex nested array examples correctly', async () => {
+ const responseSchema = getResponsePayloadSchema(amf, '/productOrderItems', 'get');
+
+ element.amf = amf;
+ element.type = responseSchema;
+ element.mediaType = 'application/json';
+ await aTimeout(0);
+
+ const exampleDoc = element.shadowRoot.querySelector('api-resource-example-document');
+ assert.exists(exampleDoc, 'Example document should be rendered');
+
+ // Verify the component received the correct props
+ assert.equal(exampleDoc.mediaType, 'application/json');
+ assert.isDefined(exampleDoc.examples, 'Examples should be defined');
+ assert.exists(element.shadowRoot.querySelector('.examples'), 'Examples section should exist');
+ });
+ });
+ });
+});