{"action":"created","issue":{"url":"https://api.github.com/repos/typestack/class-transformer/issues/203","repository_url":"https://api.github.com/repos/typestack/class-transformer","labels_url":"https://api.github.com/repos/typestack/class-transformer/issues/203/labels{/name}","comments_url":"https://api.github.com/repos/typestack/class-transformer/issues/203/comments","events_url":"https://api.github.com/repos/typestack/class-transformer/issues/203/events","html_url":"https://github.com/typestack/class-transformer/issues/203","id":381343853,"node_id":"MDU6SXNzdWUzODEzNDM4NTM=","number":203,"title":"Add support to AND and OR groups","user":{"login":"lazarljubenovic","id":7661457,"node_id":"MDQ6VXNlcjc2NjE0NTc=","avatar_url":"https://avatars3.githubusercontent.com/u/7661457?v=4","gravatar_id":"","url":"https://api.github.com/users/lazarljubenovic","html_url":"https://github.com/lazarljubenovic","followers_url":"https://api.github.com/users/lazarljubenovic/followers","following_url":"https://api.github.com/users/lazarljubenovic/following{/other_user}","gists_url":"https://api.github.com/users/lazarljubenovic/gists{/gist_id}","starred_url":"https://api.github.com/users/lazarljubenovic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lazarljubenovic/subscriptions","organizations_url":"https://api.github.com/users/lazarljubenovic/orgs","repos_url":"https://api.github.com/users/lazarljubenovic/repos","events_url":"https://api.github.com/users/lazarljubenovic/events{/privacy}","received_events_url":"https://api.github.com/users/lazarljubenovic/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2018-11-15T21:19:55Z","updated_at":"2018-11-16T09:15:40Z","closed_at":null,"author_association":"NONE","body":"First of all, thanks for the amazing lib. I love its potential! I'm fiddling with the groups functionality and I really like how it enables me to keep my \"transformers\" in on the class itself. When the model updates and I need to add another field, I write how that field will be visible to different \"views\" immediately instead of hunting down ten thousand transformer files.\r\n\r\n# Details and backstory\r\n\r\nI'm trying to find a good pattern to transform data for sending an API response, but I'm finding limitations since the \"groups\" are doing the OR operator between them. This sort-of-a-feature-request and sort-of-a-question and sort-of-a-proposal at the same time.\r\n\r\nFor example, my User model has (at least) two \"axes\" on how we can categorize the response which includes the user of id `1`.\r\n\r\n- How is user `1` related to `me`? (Is `1` actually `me`, are we friends on the platform or are we strangers?) This gives us three categories: **me**, **friend** and **stranger**.\r\n- Am I fetching this user as part of a list (eg. search results, less info) or a single user (full profile info)? This gives us two categories: **list** and **single**.\r\n\r\nNotice how the first three categories cover the entire spectrum of possible users. Also, it's not possible that a user is in two of the three categories at the same time -- they are disjunctive. Same with the last two. \r\n\r\n# Problem\r\n\r\nThe problem is that I cannot say that a certain field is visible only when fetching my own info (**me**) in the single view (**single**). If I say `@Expose({ groups: ['me', 'single'] })`, that would mean **me** OR **single**, so fetching a stranger's info on the single view would include that field as well.\r\n\r\nAs the lib currently stands, to do what I want to do would mean having to create the Cartesian product of all categories. Instead of **me**, **friend**, **stranger**, **list** and **single**, I'd need to have **me-single**, **me-list**, **friend-single**, **friend-list**, **stranger-single**, **stranger-list**. The problem is that this list grows quickly and is very difficult to maintain.\r\n\r\n# Proposal 1\r\n\r\nOne way I see this possible is to introduce some sort of query builder for the AND/OR expressions.\r\n\r\n```ts\r\n@Expose({\r\n  groups: and(or('me', 'friend'), 'single') // ['me-single', 'friend-single']\r\n})\r\n\r\nclassToPlain(User, user, { groups: ['me', 'single'] }) // matches\r\nclassToPlain(User, user, { groups: ['friend', 'list'] }) // doesn't match (requires single)\r\n```\r\n\r\nBut this is a bit wonky and not very scalable. I like the second idea much better.\r\n\r\n# Proposal 2\r\n\r\nThe other way would be to make `groups` a function instead. I think this is a more flexible approach and also probably much easier to implement in `class-transformer` itself. Just pass in an array of groups and let the function (which returns a boolean) decide if it's included or not.\r\n\r\n```ts\r\n@Expose({\r\n  groups: groups => (groups.some(g => g == 'me') || groups.some(g => g == 'friend')) && groups.some(g => g == 'single'),\r\n})\r\n\r\n// usage same as previous\r\nclassToPlain(User, user, { groups: ['me', 'single'] }) // matches\r\nclassToPlain(User, user, { groups: ['friend', 'list'] }) // doesn't match (requires single)\r\n```\r\n\r\nBasically, the current implementation accepts `groups` as `string[]` and then just checks if there's an intersection:\r\n\r\nhttps://github.com/typestack/class-transformer/blob/c3cd7f935adaee70c12d89db7327e51ce9c6679a/src/TransformOperationExecutor.ts#L401\r\n\r\nMy suggestion is that, if `groups` satisfies `typeof groups == 'function'`, we just call `this.options.groups(groups)`. I haven't tested it at all, but it seems like that's the only thing that needs to change... I think. I'll give it a shot soon.\r\n\r\nIf would be up to developer to define more complex utility functions to enable syntax like in Proposal 1."},"comment":{"url":"https://api.github.com/repos/typestack/class-transformer/issues/comments/439330368","html_url":"https://github.com/typestack/class-transformer/issues/203#issuecomment-439330368","issue_url":"https://api.github.com/repos/typestack/class-transformer/issues/203","id":439330368,"node_id":"MDEyOklzc3VlQ29tbWVudDQzOTMzMDM2OA==","user":{"login":"MTschannett","id":18646672,"node_id":"MDQ6VXNlcjE4NjQ2Njcy","avatar_url":"https://avatars0.githubusercontent.com/u/18646672?v=4","gravatar_id":"","url":"https://api.github.com/users/MTschannett","html_url":"https://github.com/MTschannett","followers_url":"https://api.github.com/users/MTschannett/followers","following_url":"https://api.github.com/users/MTschannett/following{/other_user}","gists_url":"https://api.github.com/users/MTschannett/gists{/gist_id}","starred_url":"https://api.github.com/users/MTschannett/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MTschannett/subscriptions","organizations_url":"https://api.github.com/users/MTschannett/orgs","repos_url":"https://api.github.com/users/MTschannett/repos","events_url":"https://api.github.com/users/MTschannett/events{/privacy}","received_events_url":"https://api.github.com/users/MTschannett/received_events","type":"User","site_admin":false},"created_at":"2018-11-16T09:15:40Z","updated_at":"2018-11-16T09:15:40Z","author_association":"COLLABORATOR","body":"thank you for writing this detailed proposal. \r\n\r\nI like the first approach with the declarative functions more. \r\n\r\nBut this is only my opinion. Having a few helper functions wouldn't be to much work.\r\n\r\nI think having and(), or(), not() would cover many of the cases.\r\n"}}
{"actor":{"display_login":"MTschannett"}}