awslabs / dynamodb-data-mapper-js

A schema-based data mapper for Amazon DynamoDB.
https://awslabs.github.io/dynamodb-data-mapper-js/
Apache License 2.0
816 stars 106 forks source link

Data marshaller options not passed to marshaller #166

Open djorg83 opened 5 years ago

djorg83 commented 5 years ago

If I pass the option of { unwrapNumbers: true } it is not sent to the auto-marshaller when unmarshalling an item.

Is this project dead? This commit was made over a year ago and has not been released yet. The current version 0.7.3 does not pass in the marshaller options like the documentation suggests.

From Docs

// optionally, you may specify configuration options for the
// @aws/dynamodb-auto-marshaller package's Marshaller class:
unwrapNumbers: false,

What I'm trying to do

@attribute({ type: 'Any', unwrapNumbers: true })
public car:  Car = new Car();

According to the docs, the above should unwrap numbers into primitives and not a NumberValue. But I've confirmed via debugging that the unwrapNumbers option is not being sent because release 0.7.3 is missing this commit.

djorg83 commented 5 years ago

Failed Solution A

First I tried extending the @attribute annotation to always pass in the unwrapNumbers: true option. But this doesn't work because these options are not passed into the marshaller.

export function unwrappedAttribute(parameters?: Partial<SchemaType>): PropertyAnnotation {
  return attribute(Object.assign({ unwrapNumbers: true }, parameters || {}));
}

Failed Solution B

Then I tried making my own @number annotation which has a type of Custom and it's own marshall and unmarshall functions. But this only works for top level attributes. The auto-marshaller handles any attributes that are nested documents, and doesn't use the provided marshall and unmarshall options even if the nested attributes are annotated with these options.

export function number(parameters?: Partial<SchemaType>): PropertyAnnotation {

  const marshall = (input: any): AttributeValue => {
    return { N: input };
  };

  const unmarshall = (item: AttributeValue): number => {
    if (typeof item.N === 'number') {
      return Number(item.N);
    }
    return null;
  };

  return attribute(
    Object.assign({  type: 'Custom', marshall, unmarshall }, parameters || {})
  );
}

Workaround

The only way I've been able to work around this problem is by passing objects returned from the data mapper through my own unwrapNumbers function.

export function unwrapNumbers(value: any): any {
  if (value == null || value instanceof Date) {
    return value;
  }
  if (NumberValue.isNumberValue(value)) {
    return value.valueOf();
  }
  if (Array.isArray(value)) {
    return value.map((i) => unwrapNumbers(i));
  }
  if (typeof value === 'object') {
    return Object.keys(value).reduce((map, key) => {
      return Object.assign(map, { [key]: unwrapNumbers(value[key]) });
    }, {});
  }
  return value;
}
// find one
const car: Car = await mapper.get(item).then(unwrapNumbers);
// find all
const cars = [];

for await (const item of mapper.scan(Car)) {
  cars.push(unwrapNumbers(item));
}

Suggestions

djorg83 commented 4 years ago

It looks like this repo is in fact dead. The primary contributor no longer works for Amazon. His linkedin profile shows that he switched jobs in July, which is also the time of the last commit to this repository. I'm going to see if I can find out if anyone else at Amazon plans to pick this up.

sblackstone commented 4 years ago

Ugh, can this please get merged!?

skrosoft commented 3 years ago

Thank you @djorg83 ! I had to improve your script, the reduce method was killing my instances.

export function unwrapNumbers(value: any): any {
    if (value == null || value instanceof Date) {
        return value;
    }
    if (NumberValue.isNumberValue(value)) {
        return value.valueOf();
    }
    if (Array.isArray(value)) {
        return value.map((i) => unwrapNumbers(i));
    }
    if (typeof value === 'object') {
        for (const key in value) {
            if (value.hasOwnProperty(key)) {
                value[key] = unwrapNumbers(value[key]);
            }
        }
    }
    return value;
}
ArsenyYankovsky commented 1 year ago

We have been using this ODM in production, but seeing it wasn't supported anymore we decided to fork this package and our fork is called nova-odm.

What we have done so far:

The overall goal is to keep it interface-compatible with this project (since we generally like this API), but keep it up-to-date and supported. The switch is rather easy due to interface compatibility, you just need to replace depencendies and imports like this:

@aws/dynamodb-data-mapper -> @nova-odm/mapper
@aws/dynamodb-data-mapper-annotations -> @nova-odm/annotations
@aws/dynamodb-expressions -> @nova-odm/expressions

Feel free to raise issues and send us PRs.