deepkit-framework icon indicating copy to clipboard operation
deepkit-framework copied to clipboard

Feature snake_case to camelCase conversion option

Open abrarazeem opened this issue 6 years ago • 6 comments

I went through docs i couldn't find anything that let me convert snake_case to CamelCase using plainToClass In many cases json returning from API's are mostly snake_case but my classes contain CamelCase properties. it would be nice to have something like below. Just a thought can be implement in @f decorator as well to be able to covert in both plainToClass and classToPlain.

e.g const post = plainToClass(Post, postJson, {snakeToCamel: true});

abrarazeem avatar Feb 07 '20 07:02 abrarazeem

Found

class Test {
  constructor(
    @f.asName('alias_field')
    field: string;
  ) {}
}

But it works only with constructor, why it not works with class directly?

class Test {
  @f.asName('alias_field')
  field: string;
}

darky avatar Feb 07 '20 12:02 darky

asName is only for necessary when code is minimized (constructor argument names are usually changed then, which would make getting fields as constructor args impossible, and thus strict mode impossible).

However, I agree that a mapping of field names is a good feature, but I don't know yet how to change the API and the serialize engine to keep everything performant.

marcj avatar Feb 07 '20 15:02 marcj

@darky @marcj thanks, yes it's working with constructor, @marcj totally agree with keeping performance up, FYI i recently moved from class-transformer for my recent project, this is the only missing piece for now i will go with constructor way.

abrarazeem avatar Feb 07 '20 15:02 abrarazeem

Constructor with asName is not a great replacement for your need. Better is to way for the weekend, then I think we figured something out.

marcj avatar Feb 07 '20 15:02 marcj

I thought about that and I think that this feature pretty much makes the whole codebase of converting, validation, property definition way too complex. It's probably easier to write a converter yourself, simply because if you do it in your code directly, its code is quite simple compared to the complexity it would bring to Marshal as a whole (when deeply integrated in the classToPlain/plainToClass API and decorators).

Code that covers all data structures could look like that:

function underscoreToCamelCase(name: string): string {
    return name.replace(/_([a-z])/g, c => c[1].toUpperCase());
}

function camelCaseToUnderscore(name: string): string {
    return name.replace(/([A-Z])/g, '_$1').toLowerCase();
}

function isObject(obj: any): obj is { [name: string]: any } {
    if (obj === null) return false;
    return ((typeof obj === 'function') || (typeof obj === 'object' && !Array.isArray(obj)));
}

function convertNames(item: any, nameStrategy: (v: string) => string): any {
    if (Array.isArray(item)) return item.map((v: any) => convertNames(v, nameStrategy));

    if (!isObject(item)) return item;

    const res: { [name: string]: any } = {};
    for (const i in item) {
        if (!item.hasOwnProperty(i)) continue;
        res[nameStrategy(i)] = convertNames(item[i], nameStrategy);
    }
    return res;
}

const plain = {
    id: 123,
    first_name: 'Peter',
    sub: {
        my_other_name: 'Guschdl'
    },
    items: [
        'string', 'another'
    ],
    childs: [
        {its_name: 'a'},
        {its_name: 'b'}
    ]
};

console.log(convertNames(plain, underscoreToCamelCase));
/*
    {
      id: 123,
      firstName: 'Peter',
      sub: { myOtherName: 'Guschdl' },
      items: [ 'string', 'another' ],
      childs: [ { itsName: 'a' }, { itsName: 'b' } ]
    }
*/

//and convert back again
console.log(convertNames(convertNames(plain, underscoreToCamelCase), camelCaseToUnderscore));
/*
    {
      id: 123,
      first_name: 'Peter',
      sub: { my_other_name: 'Guschdl' },
      items: [ 'string', 'another' ],
      childs: [ { its_name: 'a' }, { its_name: 'b' } ]
    }
*/

//how to work with plainToClass/classToPlain
plainToClass(MyClass, convertNames(plain, underscoreToCamelCase));

const item = new MyClass;
convertNames(classToPlain(MyClass, item), camelCaseToUnderscore);

This API makes it easy to include exceptions or your own naming strategy.

We could add those utility methods in Marshal, but only as an addition to classToPlain/plainToClass, like shown in the code above.

marcj avatar Feb 11 '20 15:02 marcj

I was looking through the tickets and maybe what I wrote in the past can help. I once wrote an ultra-fast snakeCase, camelize etc. conversion library https://github.com/encharm/xcase - it has pretty much a close to native performance.

Rush avatar Jan 07 '21 05:01 Rush

Naming strategy was implemented a long time ago. I'm going to close this, but feel free to reopen if anything else needs to be addressed.

marcj avatar Dec 03 '22 14:12 marcj