Repository: kubernetes-client/javascript
Pull request #2125 (2024-12-24 15:39:52)

fix: KubernetesObjectSerializer::deserialize() should support list api
- Added a new KubernetesObjectList class to handle list APIs with appropriate schema attributes (apiVersion, kind, metadata, and items).
- Updated the deserialization logic to instantiate and populate KubernetesObjectList instances when appropriate.
- Added test cases to validate the deserialization of Kubernetes object lists, ensuring correctness and compatibility with unknown object schemas.


Patch:
diff --git a/src/serializer.ts b/src/serializer.ts
index 210bde32..5769fac8 100644
--- a/src/serializer.ts
+++ b/src/serializer.ts
@@ -1,5 +1,6 @@
 import { ObjectSerializer } from './api';
 import { V1ObjectMeta } from './gen/model/v1ObjectMeta';
+import { V1ListMeta } from './gen/model/v1ListMeta';
 
 type AttributeType = {
     name: string;
@@ -37,9 +38,52 @@ class KubernetesObject {
     ];
 }
 
+class KubernetesObjectList {
+    /**
+     * APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+     */
+    'apiVersion'?: string;
+    /**
+     * Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+     */
+    'kind'?: string;
+    'metadata'?: V1ListMeta;
+    'items'?: KubernetesObject[];
+
+    static attributeTypeMap: AttributeType[] = [
+        {
+            name: 'apiVersion',
+            baseName: 'apiVersion',
+            type: 'string',
+        },
+        {
+            name: 'kind',
+            baseName: 'kind',
+            type: 'string',
+        },
+        {
+            name: 'metadata',
+            baseName: 'metadata',
+            type: 'V1ListMeta',
+        },
+    ];
+}
+
 const isKubernetesObject = (data: unknown): boolean =>
     !!data && typeof data === 'object' && 'apiVersion' in data && 'kind' in data;
 
+const isKubernetesObjectList = (data: any): boolean => isKubernetesObject(data) && 'items' in data;
+
+const getClassOfKubernetesInstance = (data: any): any => {
+    if (!isKubernetesObject(data)) {
+        return null;
+    }
+    if (isKubernetesObjectList(data)) {
+        return KubernetesObjectList;
+    }
+    return KubernetesObject;
+};
+
 /**
  * Wraps the ObjectSerializer to support custom resources and generic Kubernetes objects.
  */
@@ -90,12 +134,13 @@ class KubernetesObjectSerializer {
             return obj;
         }
 
-        if (!isKubernetesObject(data)) {
+        const kubernetesClass = getClassOfKubernetesInstance(data);
+        if (!kubernetesClass) {
             return obj;
         }
 
-        const instance = new KubernetesObject();
-        for (const attributeType of KubernetesObject.attributeTypeMap) {
+        const instance = new kubernetesClass();
+        for (const attributeType of kubernetesClass.attributeTypeMap) {
             instance[attributeType.name] = ObjectSerializer.deserialize(
                 data[attributeType.baseName],
                 attributeType.type,
@@ -108,6 +153,10 @@ class KubernetesObjectSerializer {
             }
             instance[key] = value;
         }
+        if (instance.items) {
+            instance.items = instance.items.map((item: any) => this.deserialize(item, ''));
+        }
+
         return instance;
     }
 }


Test patch:
diff --git a/src/serializer_test.ts b/src/serializer_test.ts
index 6023519c..5c939dc5 100644
--- a/src/serializer_test.ts
+++ b/src/serializer_test.ts
@@ -186,5 +186,66 @@ describe('KubernetesObjectSerializer', () => {
             const res = KubernetesObjectSerializer.serialize(s, 'unknown');
             expect(res).to.deep.equal(s);
         });
+
+        it('should deserialize a list of unknown objects', () => {
+            const s = {
+                apiVersion: 'v1alpha1',
+                kind: 'MyCustomResourceList',
+                metadata: {
+                    resourceVersion: '1',
+                },
+                items: [
+                    {
+                        apiVersion: 'v1alpha1',
+                        kind: 'MyCustomResource',
+                        metadata: {
+                            name: 'k8s-js-client-test',
+                            namespace: 'default',
+                            creationTimestamp: '2022-01-01T00:00:00.000Z',
+                        },
+                        data: {
+                            key: 'value',
+                        },
+                    },
+                ],
+            };
+            const res = KubernetesObjectSerializer.deserialize(s, 'v1alpha1MyCustomResource');
+            expect(res).to.deep.equal({
+                apiVersion: 'v1alpha1',
+                kind: 'MyCustomResourceList',
+                metadata: {
+                    _continue: undefined,
+                    remainingItemCount: undefined,
+                    resourceVersion: '1',
+                    selfLink: undefined,
+                },
+                items: [
+                    {
+                        apiVersion: 'v1alpha1',
+                        kind: 'MyCustomResource',
+                        metadata: {
+                            name: 'k8s-js-client-test',
+                            namespace: 'default',
+                            creationTimestamp: new Date('2022-01-01T00:00:00.000Z'),
+                            uid: undefined,
+                            annotations: undefined,
+                            labels: undefined,
+                            finalizers: undefined,
+                            generateName: undefined,
+                            selfLink: undefined,
+                            resourceVersion: undefined,
+                            generation: undefined,
+                            ownerReferences: undefined,
+                            deletionTimestamp: undefined,
+                            deletionGracePeriodSeconds: undefined,
+                            managedFields: undefined,
+                        },
+                        data: {
+                            key: 'value',
+                        },
+                    },
+                ],
+            });
+        });
     });
 });