Skip to content

DynamoDBDocumentClient throws on marshalling of class instances where class includes bound functions or arrow functions, despite convertClassInstanceToMap = true #5686

Closed
@firekart

Description

@firekart

Checkboxes for prior research

Describe the bug

[ Edited to include bound functions ]
An error is now thrown If bound or arrow functions are included as part of a class, when an instance of that class is used in PutCommand, BatchWriteCommand, DeleteCommand, etc. Example of error 'Unsupported type passed: () => this.props. Pass options.convertClassInstanceToMap=true to marshall typeof object as map attribute.'

SDK version number

@aws-sdk/lib-dynamodb@3.490.0

Which JavaScript Runtime is this issue in?

Node.js

Details of the browser/Node.js/ReactNative version

v18.17.1

Reproduction Steps

import { DynamoDBClient, DynamoDBClientConfig, } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand, TranslateConfig } from "@aws-sdk/lib-dynamodb";

const datastoreParams: DynamoDBClientConfig = {
    region: 'ap-southeast-2'
};

const marshallOptions: TranslateConfig[ 'marshallOptions' ] = {
    convertEmptyValues: false,
    removeUndefinedValues: true,
    convertClassInstanceToMap: true,
};

const unmarshallOptions: TranslateConfig[ 'unmarshallOptions' ] = {
    wrapNumbers: false,
};

const translateConfig: TranslateConfig = { marshallOptions, unmarshallOptions };
const dynamoDb = new DynamoDBClient(datastoreParams);
const dynamoDbConfig = { client: dynamoDb };

const docClient = DynamoDBDocumentClient.from(dynamoDbConfig.client, translateConfig);

/** a function to be bound to X */
function boundGetProps( this: X ) {
    return this.props
}

/** a dummy class that conforms to our schema */
class X {
    constructor({ sk, pk, ...others }: Record<string, any>) {
        this.sk = sk
        this.pk = pk
        this.props = others
        /** binding this function will cause  Unsupported type passed error, despite convertClassInstanceToMap: true  */
        this.boundGetProps = boundGetProps.bind( this )
        /* commenting out this binding and the boundGetProps below will give the expected */

    }
    sk: string
    pk: string
    props: Record<string, any>

    _position: { lat: number, lng: number} | undefined

    boundGetProps: () => Record<string, any>

    /* this arrow fn will cause Unsupported type passed error, despite convertClassInstanceToMap: true  */
    arrowGetProps = () => this.props
    /* commenting this arrow fn out give the expected */

    /** functions expressions are ok */
    normalGetProps() {
        return this.props
    }

    /** Getter Setters are OK */
    get position() {
        return this._position
    }

    set position( position:{ lat: number, lng: number} | undefined ) {
        this._position = position
    }
}


const main = async () => {
    const x = new X({ pk: 'X:1', sk: 'X:1',  })
    x.position = { lat: 10, lng: -22 }
    try {
        const putRes = await docClient.send(new PutCommand({
            TableName: 'i3012-dev-main-table',
            Item: x
        }))
        console.log('putRes', putRes)
    } catch (e: any) {
        console.log(e)
    }
}

(global as any).main = main()

Observed Behavior

Error: Unsupported type passed: () => this.props. Pass options.convertClassInstanceToMap=true to marshall typeof object as map attribute.
at convertToAttr (/Volumes/wx/code/iotv/lws/node_modules/@aws-sdk/util-dynamodb/dist-cjs/convertToAttr.js:53:11)
at marshall (/Volumes/wx/code/iotv/lws/node_modules/@aws-sdk/util-dynamodb/dist-cjs/marshall.js:6:62)
at marshallFunc (/Volumes/wx/code/iotv/lws/node_modules/@aws-sdk/lib-dynamodb/dist-cjs/commands/utils.js:64:71)
at processObj (/Volumes/wx/code/iotv/lws/node_modules/@aws-sdk/lib-dynamodb/dist-cjs/commands/utils.js:12:20)
at /Volumes/wx/code/iotv/lws/node_modules/@aws-sdk/lib-dynamodb/dist-cjs/commands/utils.js:56:32
at Array.reduce ()
at processAllKeysInObj (/Volumes/wx/code/iotv/lws/node_modules/@aws-sdk/lib-dynamodb/dist-cjs/commands/utils.js:55:32)
at processObj (/Volumes/wx/code/iotv/lws/node_modules/@aws-sdk/lib-dynamodb/dist-cjs/commands/utils.js:23:24)
at processKeysInObj (/Volumes/wx/code/iotv/lws/node_modules/@aws-sdk/lib-dynamodb/dist-cjs/commands/utils.js:44:32)
at marshallInput (/Volumes/wx/code/iotv/lws/node_modules/@aws-sdk/lib-dynamodb/dist-cjs/commands/utils.js:65:12)

Expected Behavior

putRes {
'$metadata': {
httpStatusCode: 200,
requestId: 'NM3UDHU0BFC0766O6NBHE8V6GNVV4KQNSO5AEMVJF66Q9ASUAAJG',
extendedRequestId: undefined,
cfId: undefined,
attempts: 1,
totalRetryDelay: 0
}
}

Possible Solution

No response

Additional Information/Context

This seems to be a change in behavior from recent versions of @aws-sdk/lib-dynamodb. It was picked up because of a manual change made to a Lamdba's environment variables in the aws console. Which presumably allowed lambda to relaunch with new package versions.

I am not sure if its a bug or by design, can't find any reference to it.

Metadata

Metadata

Assignees

Labels

bugThis issue is a bug.p2This is a standard priority issuequeuedThis issues is on the AWS team's backlogworkaround-availableThis issue has a work around available.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions