指定したキーだけ変換するutility
https://www.typescriptlang.org/play?#code/MYewdgzgLgBBCWBbADgGwKYHkBGArGAvDAN4BQMMYArogFwwAMANOTACYCGU69A5AEwN+AZgC0DAIyiJAFgAqQ2g2FLhAOgCcAVgBsALV4sAvqVKhIsMOmg58RMhRB56DijG4APKH0OtHeABEuHhgBITFJaXlFZVVNXQNWI2NTc2gYDgAnTNtCElYszKDuegBtMJFxKVkFflU47X1eAF0WCkLbMtcKT29Q3zcM7NtikIqI6ui62OV4pqTW0hMzcHSoKjQsPDzXdc3RsvGqqNr62caDJlDBSsiamJVzhJaMiBhS6Ez4MABzK8-vj9Fj0NhhOu8-PlBj10F4fG1oXswYFgnwbhMTg8Gs9IclId03NQ6IwEYMkVtcAdruFjvdpo91BdeLjWM1Xu9iO5YX0Ab8ANzuUEUqm8n4wZIkSg0ehE7DoTIC8kjVFwKBfX7i5pLVKrWCgFAYYDBNi5eysJy4ACC2RckMKXUhMLh-VJbgt1pyKJK1Nuk1OM0ZOMGRmBbnJZUdUOhTr6vAG0cFm2V3qOdymZ0D82jeOjBMGRPozEjIKTXrG6Np6YDc0S2chbI4b1KnN69FFiqFyZCovFV05BaliDlCsTyMpKp7IaS2pWFhgiHlP3QJu2Zooag3CANFIRG7UVhseF3G46R9Ye6VZ-XG-1myN3BXuBnUAAnsh0DBbOhgFAANLoF8AAUuAACwgAAeOQYG5dAwDYN4LW-KAAD48igmC4LeDgwBfUotQoAB+Qdh2grxYPgmA5HKDBfigECWkhAB6RiYEAWUTAHQlQA7BkAFg1AAgVSEiPQsjMJgAAKb4ADN5RgABVABKPDIyImTSO4ETEJ-YsYCI3g8N4GAAB8YAAAzwtQABJiC-H9-yA0CIJk5CjGMrS+D0yMZXQAA3eUmJYwB+hkAdYZAGuGHj+MGeghLUijSj3STpIAJWsKArnizJZPw6FlNU8iELwJCtJ0vTDJMszLOsv8AOAuiHKc4yStMyykugajYJ+OiWhDCyrPymyqvs8DHKMZySoq2zqrA8DmpQ1zQmKozGuIabWto+jmhDeqjLG-qaqm5LkI8yhvN8ihbUGUpfxgb4YAAawAkAJMo5p6EujCKNFJTKIutk3qwnDFITbSYEuhbLN-Ixyt6yq7N2qjf2aOrZrhn7hIojSoC0wjgYasGjG67aYcm5HEcB07gcOqwfMyJJSjul8Hqe0xX3fGBMmsKhUFgIgCYmiDmfQBnb0NY1bAO2d0k4bg8isAB3GBRlEuS+R1SAQAwNRUBAH5RMl9A1CgEAAEkAGVMGNtVAUVuSVYgNW9c17XZfl4JRNTP0sSeJo5LkoA
https://www.typescriptlang.org/play?#code/MYewdgzgLgBBCWBbADgGwKYHkBGArGAvDAN4BQMMYArogFwwAMANOTACYCGU69A5AEwN+AZgC0DAIyiJAFgAqQ2g2FLhAOgCcAVgBsALV4sAvqVKhIsMOmg58RMhRB56DijG4APKH0OtHeABEuHhgBITFJaXlFZVVNXQNWI2NTc2gYDgAnTNtCElYszKDuegBtMJFxKVkFflU47X1eAF0WCkLbMtcKT29Q3zcM7NtikIqI6ui62OV4pqTWguyAkCpsDFGyvxhywUrImpiVWcaDZtZzkzNwdKgqNCw8PNc7h82d8aqo2vqThMNQnsJt8jg1-s0MhAdtBMvAwABzJhwKCwhGLHr3DCdHbbbpuXo+NqDdyYx64d6fA5TX7qU68bbJXHbCjUOiMImDV5YwLBPhAr6HabHWn-BkXSE7YjudBeegwuHwgDcJIeI15yNR8JgyRIlBo9FZ2HQmWVXLJ73lCO1l1SN1goBQGGAwTYuXsrCcuAAgtkXNtCl1mdLZf0OW5PT6cjySoDwgLqTMRfNBkZ0fjSVtifksz0ZX1eAMc2a1THKZMfom5oks4ys3jBqz6MwgxjVdGxvyqRXhVX6TXthCOFDSlKCRqFabSSWQpatTqpY29YgjSaVdzyerZ9akqQrmlYIhjfD0K6nu6KGpLwhHWSiZe1FYbHg75eOs-WPfi++L5eHQ9ndwp64LuphQAAnsg6AwLY6DAFAADS6BgQAClwAAWEAADxyDAeboGAbBQp6sFQAAfHkOF4QRUIcGAYGlOcFAAPxLiuuFePhhEwHI5QYAiUBoS02wAPTCTAgCyiYA6EqAHYMgAsGoAECrbCxlEcdRMAABRwgAZsaMAAKoAJQMUGLF6ex3BqcRcEtjALG8AxvAwAAPjAAAGDFqAAJMQMFwYhKHoVhemkUYrk2XwDlBga6AAG7GiJYmAP0MgDrDIA1wxyYpgz0CpFlcaU97abpABK1hQEihWZPpjHEqZ5mcUReAkTZdkOc5bked5vkIUhqECUFIWuW17neSV0C8fh8ICS0qZeT5jV+T1gWYcFRihW1XX+b1GGYaNZHhaErUucNxC7eN-GCc0qaDS5G2LX1O2laRUWULF8UUH6gylPBMBwjAADWSEgFp3HNPQ31UVxs4mdxX0QhDNF0cZOa2TA31Hd58FGJ183dQF908fBzQDftBNw6pXFWVANnMajQ0Y0Ys23Xj22k8TyPvajz1WHFmRJKUANgUDIOgRBUEAMpoPAUCYWLKJ1Wps5ImL6DIFkXAgJV8PjgiSLIZk1jGnFeRaRwqAQOg5FELLmvk1CrneRVqNIVjxDK6rmTq5kLuO7toWsCxX1IUi97qXrBuZEbWsolQUEB27atQBrEJlM0BnB5ex5QFtWG7aR1VlNb5ykOBkEwBLqBS5GHDM1hm2BfLkMogqDGW07NcN8Ojt1wJ6dqD7pUd9r8IMdVAfd2hvfl5X2TV9nD3QHnrAp6YxeizAmdz+Pg+zq3U9QFXNcy5L0vj0ivBqLwpFPavpeq31eQb0tvBaSAIBqIUHnYFQABel+kKJHwX4gABLwQoICHJny-r-IuN8oJoRAMePIoxB6UxRtHKC9ATZm3QCLUumc5CiwAEJgX8thFBOMkRby1kzOecgr55G5vFWBMB9YQCoKgWARAaFLRLugIWb5cDX14TALSqAuB5HypeUohpjRIlnK0GA95SjyKLvudgwQGHoAAO4wFGOpAyipbSQBABgNQqAEHqU4NwNQicACSYtMDWwVPogyRiIAmPQGYixVgdF6LLCCIUYImgGQMkAA
https://www.typescriptlang.org/play?#code/MYewdgzgLgBBCWBbADgGwKYHkBGArGAvDAN4BQMMYArogFwwAMANOTACYCGU69A5AEwN+AZgC0DAIyiJAFgAqQ2g2FLhAOgCcAVgBsALV4sAvqVKhIsMOmg58RMhRB56DijG4APKH0OtHeABEuHhgBITFJaXlFZVVNXQNWI2NTc2gYDgAnTNtCElYszKDuegBtMJFxKVkFflU47X1eAF0WCkLbMtcKT29Q3zcM7NtikIqI6ui62OV4pqTWguyAkCpsDFGyvxhywUrImpiVWcaDZtZzkzNwdKgqNCw8PNc7h82d8aqo2vqThMNQnsJt8jg1-s0MhAdtBMvAwABzJhwKCwhGLHr3DCdHbbbpuXo+NqDdyYx64d6fA5TX7qU68bbJXHbCjUOiMImDV5YwLBPhAr6HabHWn-BkXSE7YjudBeegwuHwgDcJIeI15yNR8JgyRIlBo9FZ2HQmWVXLJ73lCO1l1SN1goBQGGAwTYuXsrCcuAAgtkXNtCl1mdLZf0OW5PT6cjySoDwgLqTMRfNBkZ0fjSVtifksz0ZX1eAMc2a1THKZMfom5oks4ys3jBqz6MwgxjVdGxvyqRXhVX6TXthCOFDSlKCRqFabSSWQpatTqpY29YgjSaVdzyerZ9akqQrmlYIhjfD0K6nu6KGpLwhHWSiZe1FYbHg75eOs-WPfi++L5eHQ9ndwp64LuphQAAnsg6AwLY6DAFAADS6BgQAClwAAWEAADxyDAeboGAbBQp6sFQAAfHkOF4QRUIcGAYGlOcFAAPxLiuuFePhhEwHI5QYAiUBoS02wAPTCTAgCyiYA6EqAHYMgAsGoAECrbCxlEcdRMAABRwgAZsaMAAKoAJQMUGLF6ex3BqcRcEtjALG8AxvAwAAPjAAAGDFqAAJMQMFwYhKHoVhemkUYrk2XwDlBga6AAG7GiJYmAP0MgDrDIA1wxyYpgz0CpFlcaU97abpABK1hQEihWZPpjHEqZ5mcUReAkTZdkOc5bked5vkIUhqECUFIWuW17neSV0C8fh8ICS0qZeT5jV+T1gWYcFRihW1XX+b1GGYaNZHhaErUucNxC7eN-GCc0qaDS5G2LX1O2laRUWULF8UUH6gylPBMBwjAADWSEgFp3HNPQ31UVxs4mdxX0QhDNF0cZOa2TA31Hd58FGJ183dQF908fBzQDftBNw6pXFWVANnMajQ0Y0Ys23Xj22k8TyPvajz1WHFmRJKUANgUDIOgRBUEAMpoPAUCYWLKJ1Wps5ImL6DIFkXAgJV8PjgiSLIZk1jGnFeRaRwqAQOg5FELLmvk1CrneRVqNIVjxDK6rmTq5kLuO7toWsEx2ylEG97qf58tcQWKMMTAZT+c0Blhj+ajqXrBuZEbWsolQUEsaU1vh3bGPOw7YA6ZVACyXDAGhJ4u77g0sZXUDVyeMcwG7atQBrEJlPHicwPex5QFtWG7U9gzVfQ+dawWSk7NVHN5yi5ykOBkHt5LUCRhwzNYZtgUF9r8IMUrKudxrh+zpbTu74fpSO-vAmXyiCpIgVpfFaVz+agx1W5-eEtUBS0wo-NCp93aeyRFnC2b9LyAKltvXeD1oDgPPpkUik956mFXqLGAQ8R4gLumhb+Cpr7wK3tkHeBDyGEOZkiXgaheCkXoQ5J6OD16qz6nkfBS1eBaRACANQhQPLYCoAALyYaQUSHx+EgABLwQo8iHL0NERIlea8oKFBHnkGhCjsgMWUQdZoAJoFPQ0UMTII9+A6M3phPhAjSgSGMUiY6hpjRXSgZkbObDzFmjyKUNxmQkSznUbgoechSQADVTbZ0wgASQIjKEhCJr4JLYEkrW9tiCOwAHI0EPoEtaLEzSlDyYgHuL0ebsKgmhEAx5uHoCgBEh40TUCxN4AwSRIt17hNFgAITAv5bCh9KZIlAYfJmBC5CkWvtzeK1SYD6wgFQVAsAiCTKWhooWb5cA+NwVpVAXB-H3gCTQFcwSX5olgWoUoISV77nYMEPIVgADuMBRjqQMoqW0kAQAYDUKgOp6lODcDUF3OJYtMDWwVJ8gyPyIB-PQACoFrz3nBHUmWEEQowRNAMgZIAA
https://www.typescriptlang.org/play?target=6#code/MYewdgzgLgBBCWBbADgGwKYHkBGArGAvDAN4BQMMYArogFwwAMANOTACYCGU69A5AEwN+AZgC0DAIyiJAFgAqQ2g2FLhAOgCcAVgBsALV4sAvqVKhIsMOmg58RMhRB56DijG4APKH0OtHeABEuHhgBITFJaXlFZVVNXQNWI2NTc2gYDgAnTNtCElYszKDuegBtMJFxKVkFflU47X1eAF0WCkLbMtcKT29Q3zcM7NtikIqI6ui62OV4pqTWguyAkCpsDFGyvxhywUrImpiVWcaDZtZzkzNwdKgqNCw8PNc7h82d8aqo2vqThMNQnsJt8jg1-s0MhAdtBMvAwABzJhwKCwhGLHr3DCdHbbbpuXo+NqDdyYx64d6fA5TX7qU68bbJXHbCjUOiMImDV5YwLBPhAr6HabHWn-BkXSE7YjudBeegwuHwgDcJIeI15yNR8JgyRIlBo9FZ2HQmWVXLJ73lCO1l1SN1goBQGGAwTYuXsrCcuAAgtkXNtCl1mdLZf0OW5PT6cjySoDwgLqTMRfNBkZ0fjSVtifksz0ZX1eAMc2a1THKZMfom5oks4ys3jBqz6MwgxjVdGxvyqRXhVX6TXthCOFDSlKCRqFabSSWQpatTqpY29YgjSaVdzyerZ9akqQrmlYIhjfD0K6nu6KGpLwhHWSiZe1FYbHg75eOs-WPfi++L5eHQ9ndwp64LuphQAAnsg6AwLY6DAFAADS6BgQAClwAAWEAADxyDAeboGAbBQp6sFQAAfHkOF4QRUIcGAYGlOcFAAPxLiuuFePhhEwHI5QYAiUBoS02wAPTCTAgCyiYA6EqAHYMgAsGoAECrbCxlEcdRMAABRwgAZsaMAAKoAJQMUGLF6ex3BqcRcEtjApnmZxNF0cZOZuCxvAMbwMAAD4wAABgxAAkxAwXBiEoehWF6aRRi+TZFB8B53l+QxahBSFCFIahAmRdFsUuQlzR9sSBroAAbsaIliYA-QyAOsMgDXDHJimDPQKkWVxpT3tpukAErWFASJdZk+mMcSdlUVxVlQDZY2qVxtH0SNLluYlPn+c0aV4CRYVZRhmFRTFSX+UFvXQLx+HwgJLSphtuBbZlEV7blSXpdtD0nWRcUwAVnmrSlN13eF2WPQdv3HX1Z38YJzSpqlwWbaF91A-tvnPfDGWA7t72kTZ32HaUYOnbwfEXVDqYoz5L2I5jfXY1mJXlZkrB+oMpTwTAcIwAA1khIBadxzT0Gz41QrOJncazELCxkTmLaNMBs6tQXwUY-0IxjWE8fBzS5Tj4ta-Zllo59LEK35StGLDlPq9hEs6y58Xy0G9MVRQRilNzYG8-zoEQVBADKaDwFAmF+yiBtcbOSJ++gyBZFwIBDVLkcwMhmTWMa5V5FpHCoBA6DkUQoeJ7NUK+UFg3y0hKvENHseZPHmTVxX70xawTHbKUQb3upYXh1CBa2TsEJlGFzQGWGP5qOpqfp5kmdSyiVBQSxpRF33ZvEGFTdgDpQ0ALJcMAaEntXLcoyxB9QEfJ5fTAtdx1ACfD0P49d5eADi6BQDtWFYwOTN3zDlLAsSkh7bDKEXc4pBwKQTvoHKAkYODW1etldes4GJRxjg-BOaCUQKgLpXdW69SgVxQWhXBmokSdR3j1PqFCFQMRGive8AdUBB0wmQzBdcG5IkXvnKhl5WFB0Qdbd6XDsGZFIiNMoUDoG+xgJ-b+D0yH0IRAQoRCDshIJ-phVgGiRE6I0RwqmSJeBqF4KRUxHlLGsCOsQQ0xoyakGxnI2BsdUFEEUTo3gWkQAgDUIUFK2AqAAC9SgSEKtjUSHxfEgABLwQo8SPKmOCSEoSrioKFB-nkIxCTsgMWSaEQpfDsYwMydkH+-AcnwMwj4vx4TCpIjsQ4xuzRfK8MyEvFxZSYDHigHIX2AAhMCYUNZImyUnPBCIGIEImSXHYpCkKqMRDAahu8YDvWWYwtu3F17zWcq5Qh693KFTFn0gZkFhmjJti01oGyaZOyOVLOxFcACSBEZTrxaU4pavSv4XPQFcpCGtSjvLYDKO5f98qUDKi7NwgsllSw9l7OQZz-lDJGcCm2o8kRQuKjChmADUUZL+f0jFO1sLr0mkiFRUsrY6LkKRAh5yMXXNYHIIkXjlFU2xt0+RABhNCtFjxyFJAC4ZYKZSUqlvsu5kqPBfJoCuJEAA5dAAB3AFBDWoOR2Gs2h0ABo0KGnpJh9zCbE0upLeZ8rQEdUvGImAarNW+1lmUe8grhXoFFQ8cVYF5WYUdfK1VGqtVIlNUSn2sDnU+owHkT1CJvVioxQG0oydblIn4EiUYfLYEJpFeSh6HKU4RS2XcmNvtZmlqliQ41Tz5nJ31UNTZkzNTbOYrsmVMtQG92AR5MWIjML5u9YWoGPEM3mv6k60Nlbab4t7fMl5db5WKuXI4tp00YDDtjSOy5-qPkeF0fbNwHLPoUGDWerdQrE1+oZaCg9kK+ohpdZBOdLk334tRc1etbUoTIr5l+uW9Y3Cs3ZmALmPMAMC3luvMKxsr1etvUWiWuKn3TpffnT6LUJZBhMHTbiACK2QSjVBbdrKkLSvmdSwhO1170qLZY9DWr43XoLXunR7LOVfx0WQmxFAiPoGcSRmAAA1HOS88gsvY+hTCZSvaHkyMeICpjCgrDWBsYI1jhMCZY4h8jYFZO+y9l+XApiTMNLUCZ0YAIc3CbThAKgqBYBEHo0DOTfM3y4BcfuGAyB7MZ3QBovI6kYRyimSsvO3DH6ZDC5qJKvV4QAFEPDIAMrFhhEICDkVcD5xAh9j5sDyDCNQeWr5oRC1g+u0WDKsHgHzdSpXr6FYIEQagqBUAGWzG4NOdxMjgZhIPNNKJn4HLwzAXL+WTxyDzHkRrBXSgMBGhNsrJ4V1EDmyeNQcJwUKq8j5BgrAetUD6ziQYxWIBsOAOgdSzAYAbbYFtg9r9Bj3em14MM94-Oz3KhokLKI1AXfgFdhrk22AroANR3dB29qAahLVoXHnASrDcasUBtNcCwpLslEHUh7Ha6WERpfHNMzL2XDtf2O+BvH6FtgA-gepMxvBUduBKxwZA6l1LuIR4QciX285z0C-TrnSJhLqQADqlDF2PLy4vJdsClwZYSBlmcXi0qgLg6kat7huCADAcOQDwnUn0n+DPYkBPyc0NQqSGlM5qxj249dIC+MyIgIFYE8iUd-TAajC6veud2oy0i6lPQtSRFzhFYFOtZfyCBe3sAUS0QgM7xAHv2VUrRkSOQjuk8JwU+vKUoGOb+41lI+g6lSriZCFJwFGNsI0tIlH8i81tTOPUh6Zw3EiQJ6d7n40LVs-J4qo3rrR2TuehAkAA
https://www.typescriptlang.org/play?target=6#code/MYewdgzgLgBBCWBbADgGwKYHkBGArGAvDAN4BQMMYArogFwwAMANOTACYCGU69A5AEwN+AZgC0DAIyiJAFgAqQ2g2FLhAOgCcAVgBsALV4sAvqVKhIsMOmg58RMhRB56DijG4APKH0OtHeABEuHhgBITFJaXlFZVVNXQNWI2NTc2gYDgAnTNtCElYszKDuegBtMJFxKVkFflU47X1DUMFKyJqYlWV4poBdFgpC2zLXCk9vUN83DOzbYpCKiOroutjuxsSKI36C7ICQKmwMebK-GHLWpaja+vWE3h3pi-Cq686G+6YWl-aV2-UNg8BjBeqQTGZwOkoFQ0Fg8HlXNDYSdzotXh1Vl0AZ9vm1ljc1ti+hkIOdoJl4GAAOZfcmUqmPdwwjDDc5nUZucY+YHTJEswLBPiXdF-Qk9TZuZLss4Uah0Rg8znMuG4FFo34ErHi3hnEwUXok87EdzoLz0OnUgDcTNhc0FcCgFOpMGSJEoNHocuw6Ey1r5KpRFqpLtB4LSsFAKAwwGCbFy9lYTlwAEFsi4zoURjKTWbJor-CnZgKSrirhj-trdYyxsrTtMKBzeaaJrwpvWa7biwthRr3ncmtmpe3G245fRmNmO-zVfb1fi+0SJdM9W4DRxSaVjVyHU6qX7lXaS0GXV9jWP3Yhvb6bdPA476SGkmDUpDYIgfVT0HH4QmKGp-wgUYqsC-5qFYNh4CB-5DJBrCgf6thQWokawjG3Dfrgz6kFAACeyDoDAtjoMAUAANLoDhAAKXAABYQAAPHIMDNugYBsKSSbEVAAB8eRMSxbGkhwYA4aUoIUAA-BeV7MV4rHsTAcjlBg1JQDRDxnAA9JpMCALKJgDoSoAdgyACwagAQKmcUn8XJgkwAAFJSABmPowAAqgAlGJ2ZSS5sncDZnEkZOMDeb58lCSJnnttMUm8GJvAwAAPjAAAGYkACTEERJHkVRtEMS53FGMlQUUHwcWJSlYlqBlWVkRR1FqflhXFVFMBlb0Ortp66AAG4+lpOmAP0MgDrDIA1wwmeZ0z0FZfkKaUoGOc5ABK1hQF8i2ZK54n1iFAkKQFUBBbt1kKcJonba1MXlUlqW9DVeBcTlDV0fRBVFRVqUZSt0DKaxVJqQ82z3bgj31Xlr3NRVtVPeD308SVbWhNdlW9NVmUPdlYONRD703aUX2rb9qnqb02xo9DWMvW9yVQxjdW5djcPcUF7XxXjBM-bwKn-ST2w00lFMMy9TPZt1fWZKw6ZPKRMCUjAADWFEgA5im9PQMt7aSQZeYppSkQamsZBFF07TAMs3RlpFGMDoNCwxSn681LO6-roX+XTCNSebKWW0Y5N0zD2MO70TutaVZui5QvX9VspSKzhyuq6YuH4TAADKaDwFA9Fp46bsKUGXxp+gyBZFwICbYbhcwJRmTWD6fV5A5HCoBA6C8UQueVydpLJRlG1mxR1vEMXpeZOXmTDwPcNFawElnKU2agbZOX56SrbBecBplDlvRufmMDL7X9eZI3huOlQBFSaUXdrz7xA5VPYBOZtACyXDADRX7DzPNNSe-UBP5fkRqPMuUAK7by3vvJe-4ADi6AoDPQYiLaY216C30Nq2CyW8zhlC7qCbCeECIZ1QFnVM487b0UDnRO+QYxJFxLmAiutD7zUg7oPO2d9SgD2oSw3cXwFrP2WqtPh9IxLbWvqBEhWcqGUwYWPCeXwL7twEf+aRUByEcEoXDeRTDMjcTQTg5ORCYDwMQeDXhVdWFUnYeozRlDWB2OyFopBOdM7Z2oV8XgaheDcS8XFPxrBPrEC9D6PmpBmaENTqXRqeQzGuN4A5EAIA1CFCqtgKgAAvUoEgOrM20qiJJIBmi8EKCUuKXiMmZI0lEgihQkF5HUfRUp2QxIVKRh1JRmRL7MxTnU7ISD+CNPcc0opOTOmVQyqEyevRkpdJ6cY1On4oByCIQAIRwjle2XwGlWN3GJdhuye7nB4RRUR1JVFqGniIvZYjegSMUnfM6kU3BezOZguKOtlmrPwhsrZjFSjTP6DAFB9Z1bvOOcEgeABJNipo77TPCZdGA3z1mbIovbUosK2CmmBaCrqUdxa4I4XfeOic5BfIQT89AfyMUAt3l8fFbgxYx0RhS2ppiqVoueoxO+B0viWOOYLVxchuLsNRb89FOEGKsDkMCeJFjKbM0iX0mAABhGiwlPxyGVNSjZ2LTS8sNs84FBqPAIpoFeL4AA5dAAB3al7CZphXOIIl+ILVrrSEZtFyDy4ZEx5g8O+ZrsHzX-DomAtqHVEJNmUUCGqtXoB1bCPVOEzX0QjWam19rHVfF9ZLRSiyCJRuTRgPICbqRJt1Wi9NpRq5Aq+PwL48wVUmIrdq7l4M5U1zyucqk9DI05qIYc3thtuHepJbci5h9-zXPSFO-t9y56PONcbbBq8PkdR1vY+i7ak2dqDoCy1Po8WesHdG-CYrI4bshfjYgMK4XmsNoi2ZR11WasraW-dkr00IwoHKv9MAs2Ab3amkVWLH2nugNmi97cEbM1atNYlN7ZqkjJSrCl7YpIjgoHrWWYAFZKww2rM2d8cqe3fYmsDXa9ZQbWuex1CNpq0cHJHTDiMS1EKLZRz9aKcpGuOfyjhz077Cq7X4hjw7y0fo7ZK1xsr5UINcdQwJFBOP4QidxgAai3S+cSuVydovRPpid3yZE-BhLxhR9iHGOMEAJ3H1MESIKBvjFFjNEMTghSCoRvO4HGWoPz8xmgtu43XCAVBUCwCIGJ7GJmVYwVwJE8MMBkDhYbugdReRbLknNNY2kjDx7gMyHl3cFUVpUgAKIeGQG5UrdzCC8VcClxAH8v5sDyOSNQrXAE0Ry4VieblWDwBVrZHrQCOsECINQVAqA3L5GmHXaEmQCPkk3nWx0kCXkrha21r8chmx5HG+10oDBtq7d61+M1R29tsCuY+xKSUGCsBS2l3qB2vCdcdGoCApDgDoFsswGAx2vz3ZxR4IbFAltUBW2yaYy83s9Q+7Aa+iPkdbb3oqEHbBkeKlAm9tup9MvuJy99378B-tjdu9dgA1MD27yO1DczUvvOAA3iuQ5BFhFLyyGlEFsvHZ69XqR1Z3A1ggTXWDQ9h4L2iZwfsk+8bwTnf5WvIFsrZGJNF5sS9S+lon6jNe0S+JpWyAAdUoZu94JXN5btgVu3KaTcirw+DlUBcFskNsMkIQAYCZyAKktled5Vsok5JqTWmoyqeM5XQ2IQWHcOPSASTMiIA2XzuySZ6BnS+FriAwvF1dOEhAFPZn6C2R6rpkIZ0de8Rr9nkSjWFswBS6ULXlzwvmMagaIgefhuja14QabkW5vN6hwgmHBHHTF9Lz6WySZOcrhG3ZexVyID2Pn3gZ3Y+W+vhmJtIgSYzjL6N2pIfRBYodXmyOaXBHCjdY4Or+y3BEC18TzPiuae7bP-QIgL4nekEi9k9P8fRnddQzgLtP48gtcH9etbJNILdzc2A3Ircndj9Rtjtr9swUtKRwdoCsg25YUoAqdetxkXdx9lsCNF5WpQJ79ydKcgdcDTRoFWpp9gDU908Q80kmCPBgUAC8ogCS8QDMgWCopaDsgfs-sAceCYA6cJByCudlwzhb998nwXs9945SQYs6Y1BNDN9cBOcT9ND7tgBUAqAcUIBT9tcsDFsJ9YcRxQIkwD429aISM2ChCODv8kwXC1I+DVpAD392CzMXcVwVwVCj9vcE93DS9aUcI8h6JZU+U6ZgQUMXVYsXpRVgQ5Ak8PCzM74K8q96AzFqM1JGIBUKJuI38zoIl9DpovhZc1JwUcJBDZ8StFIcjWi39XAVDojP9ODGp9Cvhg8BiGjtcWjhCvcXxIBfd0B-dA9WBejPCcpbI-MvE-NTtUYgtghmgCizD0AuiICfc-dUAA9djL4XcVCrA7UYB5gzj9ikghs48UtFjEB4jEjDZ+VZUOjhDi5YBDZjQ8M5Z0j7YDFy9K89iiiDMaUhYyizZKim8zoXQajExnBFJgQXifRfjppvjU9MSEFSADiKDJ8YAIjSAgA
https://www.typescriptlang.org/play?target=6#code/MYewdgzgLgBBCWBbADgGwKYHkBGArGAvDAN4BQMMYArogFwwAMANOTACYCGU69A5AEwN+AZgC0DAIyiJAFgAqQ2g2FLhAOgCcAVgBsALV4sAvqVKhIsMOmg58RMhRB56DijG4APKH0OtHeABEuHhgBITFJaXlFZVVNXQNWI2NTc2gYDgAnTNtCElYszKDuegBtMJFxKVkFflU47X1DUMFKyJqYlWV4poBdFgpC2zLXCk9vUN83DOzbYpCKiOroutjuxsSKI36C7ICQKmwMebK-GHLWpaja+vWE3h3pi-Cq686G+6YWl-aV2-UNg8BjBeqQTGZwOkoFQ0Fg8HlXNDYSdzotXh1Vl0AZ9vm1ljc1ti+hkIOdoJl4GAAOZfcmUqmPdwwjDDc5nUZucY+YHTJEswLBPiXdF-Qk9TZuZLss4Uah0Rg8znMuG4FFo34ErHi3hnEwUXok87EdzoLz0OnUgDcTNhc0FcCgFOpMGSJEoNHocuw6Ey1r5KpRFqpLtB4LSsFAKAwwGCbFy9lYTlwAEFsi4zoURjKTWbJor-CnZgKSrirhj-trdYyxsrTtMKBzeaaJrwpvWa7biwthRr3ncmtmpe3G245fRmNmO-zVfb1fi+0SJdM9W4DRxSaVjVyHU6qX7lXaS0GXV9jWP3Yhvb6bdPA476SGkmDUpDYIgfVT0HH4QmKGp-wgUYqsC-5qFYNh4CB-5DJBrCgf6thQWokawjG3Dfrgz6kFAACeyDoDAtjoMAUAANLoDhAAKXAABYQAAPHIMDNugYBsKSSbEVAAB8eRMSxbGkhwYA4aUoIUAA-BeV7MV4rHsTAcjlBg1JQDRDxnAA9JpMCALKJgDoSoAdgyACwagAQKmcUn8XJgkwAAFJSABmPowAAqgAlGJ2ZSS5sncDZnEkZOMDeb58lCSJnnttMUm8GJvAwAAPjAAAGYkACTEERJHkVRtEMS53FGMlQUUHwcWJSlYlqBlWVkRR1FqflhXFVFMBlb0Ortp66AAG4+lpOmAP0MgDrDIA1wwmeZ0z0FZfkKaUoGOc5ABK1hQF8i2ZK54n1iFAkKQFUBBbt1kKcJonba1MXlUlqW9DVeBcTlDV0fRBVFRVqUZSt0DKaxVJqQ82z3bgj31Xlr3NRVtVPeD308SVbWhNdlW9NVmUPdlYONRD703aUX2rb9qnqb02xo9DWMvW9yVQxjdW5djcPcUF7XxXjBM-bwKn-ST2w00lFMMy9TPZt1fWZKw6ZPKRMCUjAADWFEgA5im9PQMt7aSQZeYppSkQamsZBFF07TAMs3RlpFGMDoNCwxSn681LO6-roX+XTCNSebKWW0Y5N0zD2MO70TutaVZui5QvX9VspSKzhyuq6YuH4TAADKaDwFA9Fp46bsKUGXxp+gyBZFwICbYbhcwJRmTWD6fV5A5HCoBA6C8UQueVydpLJRlG1mxR1vEMXpeZOXmTDwPcNFawElnKU2agbZOX56SrbBecBplDlvRufmMDL7X9eZI3huOlQBFSaUXdrz7xA5VPYBOZtACyXDADRX7DzPNNSe-UBP5fkRqPMuUAK7by3vvJe-4ADi6AoDPQYiLaY216C30Nq2CyW8zhlC7qCbCeECIZ1QFnVM487b0UDnRO+QYxJFxLmAiutD7zUg7oPO2d9SgD2oSw3cXwFrP2WqtPh9IxLbWvqBEhWcqGUwYWPCeXwL7twEf+aRUByEcEoXDeRTDMjcTQTg5ORCYDwMQeDXhVdWFUnYeozRlDWB2OyFopBOdM7Z2oV8XgaheDcS8XFPxrBPrEC9D6PmpBmaENTqXRqeQzGuN4A5EAIA1CFCqtgKgAAvUoEgOrM20qiJJIBmi8EKCUuKXiMmZI0lEgihQkF5HUfRUp2QxIVKRh1JRmRL7MxTnU7ISD+CNPcc0opOTOmVQyqEyevRkpdJ6cY1On4oByCIQAIRwjle2XwGlWN3GJdhuye7nB4RRUR1JVFqGniIvZYjegSMUnfM6kU3BezOZguKOtlmrPwhsrZjFSjTP6DAFB9Z1bvOOcEgeABJNipo77TPCZdGA3z1mbIovbUosK2CmmBaCrqUdxa4I4XfeOic5BfIQT89AfyMUAt3l8fFbgxYx0RhS2ppiqVoueoxO+B0viWOOYLVxchuLsNRb89FOEGKsDkMCeJFjKbM0iX0mAABhGiwlPxyGVNSjZ2LTS8sNs84FBqPAIpoFeL4AA5dAAB3al7CZphXOIIl+ILVrrSEZtFyDy4ZEx5g8O+ZrsHzX-DomAtqHVEJNmUUCGqtXoB1bCPVOEzX0QjWam19rHVfF9ZLRSiyCJRuTRgPICbqRJt1Wi9NpRq5Aq+PwL48wVUmIrdq7l4M5U1zyucqk9DI05qIYc3thtuHepJbci5h9-zXPSFO-t9y56PONcbbBq8PkdR1vY+i7ak2dqDoCy1Po8WesHdG-CYrI4bshfjYgMK4XmsNoi2ZR11WasraW-dkr00IwoHKv9MAs2Ab3amkVWLH2nugNmi97cEbM1atNYlN7ZqkjJSrCl7YpIjgoHrWWYAFZKww2rM2d8cqe3fYmsDXa9ZQbWuex1CNpq0cHJHTDiMS1EKLZRz9aKcpGuOfyjhz077Cq7X4hjw7y0fo7ZK1xsr5UINcdQwJFBOP4QidxgAYvAVA3BMi5yyFACAAB1LONEc531riAfCmRcI5SLlAIzfbbF3z7iPJzdmn7ut-pvNOiMrBEo5XXdIRAdN6Z9IZuzpnzPNI4PFtmoQODYGwIl3gKXUteOS747jAApEAlJBa7qoNkVi5i1J9q+MmZA+E2IoZdUGDurA1UlbrmAcrNE75YOijAartW2DkamvfFrpX2vPT9hlPr8lH7FQ5aBvjFEABqLdL7FygNCqMjEBVLZW+gNbd8VqgEyGwei1czoSZG21jrfa8itiaxQcL+movGbM2pei6GOHLdQKthBXxLtleerxZ90cJY9fY+Hf7Y3aKkqI593ba2dY5S+z9qApRIcdZNuHZ1NkTVvumXfJSXM-oA0x-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
https://www.typescriptlang.org/play?target=6#code/MYewdgzgLgBBCWBbADgGwKYHkBGArGAvDAN4BQMMYArogFwwAMANOTACYCGU69A5AEwN+AZgC0DAIyiJAFgAqQ2g2FLhAOgCcAVgBsALV4sAvqVKhIsMOmg58RMhRB56DijG4APKH0OtHeABEuHhgBITFJaXlFZVVNXQNWI2NTc2gYDgAnTNtCElYszKDuegBtMJFxKVkFflU47X1DUMFKyJqYlWV4poBdFgpC2zLXCk9vUN83DOzbYpCKiOroutjuxsSKI36C7ICQKmwMebK-GHLWpaja+vWE3h3pi-Cq686G+6YWl-aV2-UNg8BjBeqQTGZwOkoFQ0Fg8HlXNDYSdzotXh1Vl0AZ9vm1ljc1ti+hkIOdoJl4GAAOZfcmUqmPdwwjDDc5nUZucY+YHTJEswLBPiXdF-Qk9TZuZLss4Uah0Rg8znMuG4FFo34ErHi3hnEwUXok87EdzoLz0OnUgDcTNhc0FcCgFOpMGSJEoNHocuw6Ey1r5KpRFqpLtB4LSsFAKAwwGCbFy9lYTlwAEFsi4zoURjKTWbJor-CnZgKSrirhj-trdYyxsrTtMKBzeaaJrwpvWa7biwthRr3ncmtmpe3G245fRmNmO-zVfb1fi+0SJdM9W4DRxSaVjVyHU6qX7lXaS0GXV9jWP3Yhvb6bdPA476SGkmDUpDYIgfVT0HH4QmKGp-wgUYqsC-5qFYNh4CB-5DJBrCgf6thQWokawjG3Dfrgz6kFAACeyDoDAtjoMAUAANLoDhAAKXAABYQAAPHIMDNugYBsKSSbEVAAB8eRMSxbGkhwYA4aUoIUAA-BeV7MV4rHsTAcjlBg1JQDRDxnAA9JpMCALKJgDoSoAdgyACwagAQKmcUn8XJgkwAAFJSABmPowAAqgAlGJ2ZSS5sncDZnEkZOMDeb58lCSJnnttMUm8GJvAwAAPjAAAGYkACTEERJHkVRtEMS53FGMlQUUHwcWJSlYlqBlWVkRR1FqflhXFVFMBlb0Ortp66AAG4+lpOmAP0MgDrDIA1wwmeZ0z0FZfkKaUoGOc5ABK1hQF8i2ZK54n1iFAkKQFUBBbt1kKcJonba1MXlUlqW9DVeBcTlDV0fRBVFRVqUZSt0DKaxVJqQ82z3bgj31Xlr3NRVtVPeD308SVbWhNdlW9NVmUPdlYONRD703aUX2rb9qnqb02xo9DWMvW9yVQxjdW5djcPcUF7XxXjBM-bwKn-ST2w00lFMMy9TPZt1fWZKw6ZPKRMCUjAADWFEgA5im9PQMt7aSQZeYppSkQamsZBFF07TAMs3RlpFGMDoNCwxSn681LO6-roX+XTCNSebKWW0Y5N0zD2MO70TutaVZui5QvX9VspSKzhyuq6YuH4TAADKaDwFA9Fp46bsKUGXxp+gyBZFwICbYbhcwJRmTWD6fV5A5HCoBA6C8UQueVydpLJRlG1mxR1vEMXpeZOXmTDwPcNFawElnKU2agbZOX56SrbBecBplDlvRufmMDL7X9eZI3huOlQBFSaUXdrz7xA5VPYBOZtACyXDADRX7DzPNNSe-UBP5fkRqPMuUAK7by3vvJe-4ADi6AoDPQYiLaY216C30Nq2CyW8zhlC7qCbCeECIZ1QFnVM487b0UDnRO+QYxJFxLmAiutD7zUg7oPO2d9SgD2oSw3cXwFrP2WqtPh9IxLbWvqBEhWcqGUwYWPCeXwL7twEf+aRUByEcEoXDeRTDMjcTQTg5ORCYDwMQeDXhVdWFUnYeozRlDWB2OyFopBOdM7Z2oV8XgaheDcS8XFPxrBPrEC9D6PmpBmaENTqXRqeQzGuN4A5EAIA1CFCqtgKgAAvUoEgOrM20qiJJIBmi8EKCUuKXiMmZI0lEgihQkF5HUfRUp2QxIVKRh1JRmRL7MxTnU7ISD+CNPcc0opOTOmVQyqEyevRkpdJ6cY1OZi5BEIAEI4RyvbL4DSrG7jEuwnZPdzg8IoqI6kqi1DTxEbssRvQJGKTvmdSKbgvanMwXFHWyy1kbIovbUo0z+gwBQfWdWbyjnBIHgASTYqaO+0zwmXVMQglZ+F1mbMYqUaFbBTSAuBV1KO4tcEcLvvHROchPnIu+eih2FFcWrWZviqwhLw7ktqUiqAKL0DrOeoxO+B0viWKOYLVxchuLsK+ain5OEGKsDkMCeJFjKbM0iX0mAABhGiwlPxyGVJy9ZWLTS8sNk8wFBqPBwpoFeL4AA5dAAB3Tl7CZphXOIIl+QLVrrSEZtFy9y4ZEx5g8O+ZrsHzX-DomAtqHVEJNmUUCGqtXoB1bCPVOEzX0QjWam19rHVfF9ZLRSiyCJRuTRgPICbqRJt1d89NpRq4Aq+PwL48wVUmIrdq75Irtl5TOVSehkac1EIOT2w23DvXEpuecw+-4rnpEnX2u5c8HnGuNtg1e7yOo63sfRdtSbO3gyUg2j10Bs3RvwmKyO67wX42IFCmF5rDbwtmUddVmrK2lr3ZK9NCMKByp-TALN-7d2ppFZi+9dKT0DrPe3BGDKorTSJVe2apJSUq3Je2KSI4KB61lmABWSs0NqzNnfHKntX2JpAwevWEG1pQcdQjaa1HByR3Q4jEtRCi3kffVSiiRqjn8o4c9O+wqD1+Lo0O8tb6O2StcbK+VCDXHUMCRQdj+EImcYAGLwFQNwTIucshQAgAAdSzjRHOd9a4gHwpkXCOUi5QAM722xd8+4jwczZp+7rf6bzTojJl-U2V13SEQLTOmfT6Zs8Z0zzSOCxbZqEDg2BsDxd4El5LXjEu+M4wAKRAJSQWO6qDZFYuYtSvavjJmQPhNiSGXVBg7qwNVRW65gFKzRO+WDoowEq9VtgpGpr3ya8V1rz0-YZR6-JR+xU2XAZ4zhAAai3S+xcoCQqjIxAVFFFuoGWwgu+K1QCZDYPRauZ0xNDZa213teRWwNYoKF3TEXDMmbUvRVDHDtu7doxdkrz1eKPujhLLrrHw4-ZG7RElBGPtLfQCtnWOVPuw4QaUMHbWTYsseauxF0y75KS5n9AG6P6wFIMhNMj27ZuSoRzDlba20AYqPdTnbSPaOudR6NsSyUL1hxgAUka40zIMeXUcsNlzx0RoHvmnnkk2TS-rPGqTn6uVSsR7T9bEamdfa+Oz2iym5duEp8rzXLO6eoFepthbNOEFfDZ8137tEjA3v9fj4mgNZlwbl0T-FIOQX4YTmhuFgOyM+-xVh+sOG5bvbkERjWRztb683obtFW2rerfW8HC3qvrcwFy-lgOvGddqQFdz-XjH9YI3BDNxXqbjcrb48hmAAna97cNtRGz8AW70WNBHvDImg4GPoGdF0YrCCsAKX3uigBrBkAFYMgARBhyoAJIZADgxoALO1ADqDIAMwZZ+AH0GQAkQyAAqGQAlwyAB+GQAFgyAHQbQApgyAEUGQA0QyAAiGQAYgyT-85kBfgAV+MAJoMrAACiHhgA7bYpvZQ7N60YT72wj4A7MovJcbSZG4p7M5q705yrQ6IEIIe5+ZB5spWDQaSYUZzZZ7Zx9KJwoTRixiIRugABESY5CqSrSqMNBRYM43AlB9A8ww+pgBSqqLkYA8A4AcgIAH6G2rkfE7CpQ0eXCr+oIBSMunkMhiMzqNk5K8hshKBoEPBfBYAAhQhf+ABVAQBLkXwoqBipAKhiMnkphOkqqB2FcbAAhxuQhihCkNhR2J21iXwZ27CGh-BghyoXelhbgPefuZKRGesRh1G0hOkjuUeIcnBVhJi-oNEIAn4eQLhdhIADhfhZA8hHA5o7hARFA2Anolq-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-bRN-bRD6e5s9qZrZFHTAxzrMg5StUlQiVpTpVnOZV6vaf7e2CTvpF4z41vuw6ZNPYAFSKgAEoqAAhbh49UyvoAIAMO9j+gA5gxT0NP86r6T48OhV-lWBeAROywQChMaIDIQ5SRZOSMO6c6R0ZSqMc79z3oIr1j03J2m5mUWVZ1XErZfCTNtY3U8XGOywinwlJh0VKUHSrniUolDWwmjXjXcXqMFO33rgIBUhgC2TECuigQzUblbnxwGXAvZhxy0r0BMOHNJhws4SApaNnMwBO157fX0wSMx2nl+55OSivnXOJWsUPWcZA22U1VCPICU3UunMt5HJ50F0ZrES2HF30hfCl0Z3l04RirsKuBBFR5yMrZEZk3RkU26FiP0v7VKPUZfBUAwqn1WBsAXqJ2wBMPvWfWGz8qyqitMsN4sud4zG4YjU4vUIQFEY8sSrK48ow1w2TlLHKq2SJjOCKTAjosILTQGuHRjmE2W5oHZwoFUtyu0uytY1KM02MkB0aMnNIv-FetQA81soPY+hIN6lEJjHuw4sW6kiGwA1GFZC9mOqj7kM6SABgSrpIZNvoAIEMhkgAhuaABvcoAAPerAmyJGvy2CtrTNwhmy-2RycgxbJxV8HCpIYsMcbgE7EsARgAJmmABommvtvrPvHIAFcMXdgAEwxsrSv6FK7Zv7QNFjvCb56UKipFvXEjt3a86VvVt1uNutvtu-KdvSrdsju9vE3SoDsN5DsXscomJSSv6IybJEp9FspROxNgUwBpuZAZt2FZvwmASdi4BeKcDcApZyjND1YvhPVMPsFEAfW-pfVcTAgFJBgz637PSAAGDNfpPoAHq+t+gAngyACyDI+9KnfNugDXrqR9YuR1R9frR4AFEMgAPfG1vb7X6ACWDF0+AcPePXPoANHqp+gAsVGAAa0YAKoME9TDgAhwyAC9DIAMMMgAkwx9OAAVxoAGtRPdfTTH0+gA8gzT68PltHuGzbrQewcU0oGFvjlsIutuu4DI2sBDXo0QCkAVMSV-lJv0BF2bL-IlGZCAo8u4WTktofM4upIQAAtAvshDhuBgu-JFOQsUTQsjgovWvoXM5+2Tl60G0lda26hEudn7MMss0nNKMpuyMMtfgrZ5BMPFVMGnisAtJFDBBxSoVVdG3lf2r63BCG3QIUCUEwRAmoxzfzCsF2QjcGUVeTerfAizf3GHDHCDe9BiTLdTdjd2oTfcBTfGAO2QjgOQMP1tdsArZXc4eP1sB4cnMG1zdfDlCFDPETL9f7C7dPH7dxR7wQiQA3eW1QOKOvfBCClAA
ObjectKeyPaths
code:typescript
// CurrentPathのないパターン
type ObjectKeyPaths<T extends object> = T extends any[]
? number extends T'length'
// 配列の場合
? T extends (infer U)[]
? U extends object
? U extends any[]
? '[]' | []${ObjectKeyPaths<U>}
: '[]' | [].${ObjectKeyPaths<U>}
: '[]'
: never
// タプルの場合
: T extends ...infer Rest, infer U
? U extends object
? U extends any[]
? '[]' | []${ObjectKeyPaths<U>} | [${Rest['length']}]${ObjectKeyPaths<U>} | ObjectKeyPaths<Rest>
: '[]' | [].${ObjectKeyPaths<U>} | [${Rest['length']}].${ObjectKeyPaths<U>} | ObjectKeyPaths<Rest>
: '[]' | [${Rest['length']}] | ObjectKeyPaths<Rest>
: never
: {
K in keyof T: K extends string
? TK extends any[]
? K | ${K}${ObjectKeyPaths<T[K]>}
: TK extends object
? K | ${K}.${ObjectKeyPaths<T[K]>}
: K
: never
}keyof T
code:typescript
// CurrentPathあるパターン
type JoinObjectKey<CurrentPath extends string, AppendKey extends string> =
CurrentPath extends ''
? AppendKey
: ${CurrentPath}.${AppendKey}
type ObjectKeyPaths<T extends object, CurrentPath extends string = ''> = T extends any[]
? number extends T'length'
// 配列の場合
? T extends (infer U)[]
?
| ${CurrentPath}[]
| (U extends object
? ObjectKeyPaths<U, ${CurrentPath}[]>
: never
)
: never
// タプルの場合
: T extends ...infer Rest, infer U
?
// []はいらないかも
| ${CurrentPath}[] | ${CurrentPath}[${Rest['length']}]
| (U extends object
? ObjectKeyPaths<U, ${CurrentPath}[] | ${CurrentPath}[${Rest['length']}]>
: never
)
| ObjectKeyPaths<Rest, CurrentPath>
: never
: {
K in keyof T: K extends string
?
| JoinObjectKey<CurrentPath, K>
| (TK extends object
? ObjectKeyPaths<TK, JoinObjectKey<CurrentPath, K>>
: never
)
: never
}keyof T
https://www.typescriptlang.org/play?target=6#code/MYewdgzgLgBBCWBbADgGwKYHkBGArGAvDAN4BQMMYArogFwwAMANOTACYCGU69A5AEwN+AZgC0DAIyiJAFgAqQ2g2FLhAOgCcAVgBsALV4sAvqVKhIsMOmg58RMhRB56DijG4APKH0OtHeABEuHhgBITFJaXlFZVVNXQNWI2NTc2gYDgAnTNtCElYszKDuegBtMJFxKVkFflU47X1DUMFKyJqYlWV4poBdFgpC2zLXCk9vUN83DOzbYpCKiOroutjuxsSKI36C7ICQKmwMebK-GHLWpaja+vWE3h3pi-Cq686G+6YWl-aV2-UNg8BjBeqQTGZwOkoFQ0Fg8HlXNDYSdzotXh1Vl0AZ9vm1ljc1ti+hkIOdoJl4GAAOZfcmUqmPdwwjDDc5nUZucY+YHTJEswLBPiXdF-Qk9TZuZLss4Uah0Rg8znMuG4FFo34ErHi3hnEwUXok87EdzoLz0OnUgDcTNhc0FcCgFOpMGSJEoNHocuw6Ey1r5KpRFqpLtB4LSsFAKAwwGCbFy9lYTlwAEFsi4zoURjKTWbJor-CnZgKSrirhj-trdYyxsrTtMKBzeaaJrwpvWa7biwthRr3ncmtmpe3G245fRmNmO-zVfb1fi+0SJdM9W4DRxSaVjVyHU6qX7lXaS0GXV9jWP3Yhvb6bdPA476SGkmDUpDYIgfVT0HH4QmKGp-wgUYqsC-5qFYNh4CB-5DJBrCgf6thQWokawjG3Dfrgz6kFAACeyDoDAABSICUrY6DAFAADS6A4QAPAAwlQ2ToGAUAAApcAAFjAzYsWwpJBl8ybIPhYBsNROE8V4fECfe1IAHyEKwjHMaxHFQNxvFiaSrZnAA-DAwmieJNFnPQAAGAAkxAqZkLHsVxRhqNZRl8RJRjmaYuH4TAZEURJ6mcRAmCoGwtFyFJ3DaTASbkVAilEBFWn8RkYA4aUoIUAZXo+pFMkwHI5QYNSGkPGcAD05UwIAsomAOhKgB2DIALBqABAq+kFXl0UABSUgAZrlACqACUGXZgZ-UdSlsUUZOMBjRNpIcGlI3ttMBm8BlvAwAAPjA5kZdZflUTRgXBaFtH9fJHkzRQfAbdtu0Zc5xCHQFXGnWFF1XStMC3b0Ortp66AAG4+hVVWAP0MgDrDIA1wxNa10z0El0nRaUoG9blABK1hQF86OZDA-WZfWc3JaSU1QDNJPIyli3pUT31rXdO17b0B14HFr0ae952XeZ917dZWPQEVLFUqVvTbGzuAc8db0hR9vP3S9stc-LtFC-F10-aETMPb0T3KzhJ1q59fPM6UgvYyLJWcQ82wG+z-kq0FJuKzthvG2dGvyTNv2beblvC7wxVi7bEu9GbvmO0dRty172M+wDlDA6DN35E8lEwJSMAANY0SAPUFb09CZ6TO70qNBWlJRBpl7Ty0rQZmfM9ZlFGFLMux6rZ2FTXvO+1XNfzTF0da03-Ot05HdO13Ls99XvT999aeUdmgMg5kSSlHnOEF0XXl4QRHtveFw-k18tn2YFw-HkQrYJe1ddLUT2U0Few+FcHovi2DNUNS1bUkZRRSt1MAfV8ZDQbqtGaLcbJMTsmpRyGVPLfR2p1caZdyZjyjtLGeJ1zpfCsnA1SDkNJGGQYnZe69U4rUGmvZOG9f5Q1hgAhGj9qYbjRmAzG2NcbcIgfTNwekYG7WspfRBZDkET2IQg0hnFyGBygNbUOdsI4iPQWfUey9Zo4M7vg-qhCxHwKvkgiO0jxFyIUcQDWyjxbbHMpQ761DN7fToag3ReCT4awvsYiRnFHFuGcawdMGcs5gFzvnQuchi4UFLhw8u1JRoiOIqRaOEkGK+LkV8SiATphoN7rXeJWDtEGWPlzcKC8vgpLAIbDJJDArZPkrk+sQTXH0KsIwrY29In72wofGAdkIBUFQFAeWtgJJ5DKXPMK3l0B7wQngH2syBnWGGaMvAnNuJECmRAWisz5kHkWQfHyABlNA8AoC0ROY6G+ckaQwBOegZAWQuAgHxmXQSMA2KDJ9CDPIPUOCoAgOgB+1z3nxKIXjGA7lrKPOeZkV5mR27EChRrL6s0zilGzKBTqEyy6th0RlbW1caK9EGvmGAOLvnWF+QRMujoqAEQMqUMFw8iEwpRfwmAABZLgwBOJfmRWivmBleVQH5V+bWcKXlQDeQaMoZKKWgQAOLoDkbs72Zwib0FZfi-6QjziCLKGC0EfTTnnKgKmBFs9dmbNJB8u5GUvjSoRbK8FwDZK7gfna4epQoWbNubuL4XDwEwA1oG+kGUX7nFAmc1AFzaKbOdU8mVbyvgMpBcG-8caLlWo4Da9WvCHkptdW8+S2rDWmDNQRVV6rE3OwjQpPIObLXZHzfg1gLa80FpbfW2eXxeBqF4PJAdG0R2sAFsQHKSK1E+2rTAZ5XM8i1vwbwHqIAQBqEKI9bAVAABepQJB-R9pVVE66QDNF4IUS9G0B27r3WVedhQTrNotbRK92QMq3p1n9dNmRGVLP6c+t6-BX3xsuWujdh7f0PWstO+xf6APHJrWquQh8ABCOEJK7LkF8F9DrdwZQfvh+JfquV4viZ8kNPD0gEcjb0aNQD8r10EU3Giw91p-UrrWtD+FMPYYqdO-oYaE70Iox62DnLQ0AEkxKmmHvBtRDMYA8Yw1hmiOHSiybYKaYTmqnEMJoSvdjZcd57zkNx1DamBO91JV8fTLTDMuO1hZ+dqm+Oz1Ppg6O2SG1lx2WrOQTTl1WY8wJ1guHWArpPpsn2c7ln0U4otT8chlS8fQJh7TpovPxJY18LLHgFNvx9F8AAcugAA7ulh+TGUbUfxt4sJobCasAMjYr+NsHjDwK21VG-5Gvlaq4fI1Mb-yJeS+gVLsJ0uZbkx4Qt0B8tzbK5V6rXwWtp1c8swbU2MB5HG9SSbaW1MFdoqUT5Qmvj8C+PMeL-SDspbU-g3DXy3qNqpE6mAO3D7Ebe2XMjobxP5So-+VF2N3tRta+wiTLG2pA+ipx-Vq1DJtrog9ybT2T6FUuyJxbX3Vs-eaSXEzEKLZSdygVorl4fT2MpjAdHu2McedO1rCgkXtEUAKxS9sDPMflMKgVvTRbvv4WadMMXLmzLQpJxJszUTK4jgoNXMJETd5RJiXEiTElsG84889ypuOcb46G6LrWiMF6DnoRZtOIv0DIfp0lw7M31N0Vq5NHz0vZ7DwCz3Edxvqv7cd49vXJ8IvAmi+U2LwJbekDuz5AAYvAEZPprlZCgBAAA6hcziVzh7fJAPhTIuEJLOqgGn97oK2WwrL0X5FYPoAeR0Sc7WHTQbzsGbAIgifk+ZFT0XzP2f30cGH-7UIHBsDYFH7wCfk+B3j+Hfb3XGWXcADVAWMseVAaTUZwq+Zwmv1AG+1XDyxqATIYVPm0z9xY6+dHnR32HUpCg3fuC95r+nrPGlaJy89wfo-Rub8uJFIy5W9nMsoCopdACNJh4f8JI-90BN9K44D18EC1VSgoDOJBFNth5YdlNp0P5bEw4tZT06o4ZsFu0GIg8mdl9kDD9UCt8d9sditMhhNaD-9DEZETFJEI4mliCIYYYyDtFEZfV6tDc+FmssDG42QOd6xQIl9+MaJ4DN9t80AFsjc2D6CfF6kgDudl55DV8UDlCd8DFf9DC1UOCMCrF2sQ47EeDWdVx7DEYB4f83dSRQDsFrcqF04Odlds4XCNd3t7DwD9CNCjDVCCk98lDzCiISIak0kaI6lZEGloVeCZDNsLdtFwR50QjFCzDLlXCR5cF1Dci6DN888094BAVaJjRfDwkfcwogsYlaYXRgsCBWBT0dlABrBkACsGQAEQYJJAAkhkAHBjQALO1AB1BkADMGXowAfQZABIhkAAqGQAS4ZAAfhkAAsGQAdBtABTBkAEUGQAaIZAAIhkADEGTo0AgYwAFfjABNBlYAAFEPBgBD8dNv8elQjoj6jwpgsQCU4wCdEcj988iVDUBd9TDSi1UAkgl50rATcCIiBfiojLl9lC4UJoxYxEI3QAAiJMK1LdT9fWTEosGcbgNE+geYFo0wU9ZZfqMAeAcAOQEARnIE8aRKB+UoaJX1UA0EU9cAkaTklzD+UgHkrkl7UCSk6ksAWk+k24+4qgR4kwoLctfkqqNhEaBU9wfpU-N5NgWkjQ+kgo9U8-WiS-NKVogmKkmkuk5UaolUpXTOPwnpaJMoSiL4ApDkqqchfwudck-pf0TiEAT8PIPUzUkAbUi0sgHkjgc0O5FgHk7AT0ZglUowD0qqBLKg53YM2EHLCTc+YExlRnYeTqUoHZd4r4WmMlIjPIdwlU5MibVMko--DM-KLMl42AMuGom0uo+IgtRo+gZohMpSHk2ElAnU+zciDUrU2so7dMps3g+deAkLKAZ3QKPZQ+Ped8TIT8DCOfPYA4I4dAeYMde3W3QPas6zBIhEm8YCUIBZXAaDNQK8+YZoW7e3QZNZSZDsz2GZZcwuGCXAOdcMBdH5TIEGFtPITqckCMoNOAEtRFcCh8HaLGKkW45AQaGC6kIlAgRSVwP8xAPlAVNgPIckNQbC8VTiUCqCt1NxLOQuTqIiiVPCggIgagVAVAQabwigOyaETIcJckQlckeVStLYVgLCnCr8OQZsPIGi3C0oBgImIS4ir8SnIgCSr8NQSkHTQrLaHaBgQS18f84GUSrwfCx0NQCAeNYAdATqZgGAJStgFSubCi9ipicJLFaYHFZAOyIGfS2AZlNyvS5sPijKclM4ayzyxUUCHy4FQChAi1UCoyky+AMy6i4StgSnAAaisqSs8rUBsM4nJUgvhURQotDBfAsBgE-HVRAp3kChQqpGQoSQ+wNHQu8Ics4tV0CjOGMuisHV4Aor-GwuQE6k6kXRysIEUnCtpRbUGq4i+HKk6gAB1ShZqyUto5qFq2BFrBpypBoerKUepUAuBOo6EwwdLHRFoIB11MhEBMMX0iBOokxuy0ovghqIBqrPsTrIBzrVz6BOogYUD7qcIWLGraZarmjGrMKdLSghqs01AO8ToGqF03pWB4AqKhrCAGLhlmLWKVkOLwk3qzq3lVzbq8AKKVwkaYBOpu0VKIBu1CbcAtrMa-zCg8gkwzhSbJroD6K74NoWKRxmrwlChCKOB+rupuBEAAbFJcaPqrq3phb0BEAvgYa3o-1TqPqfQtrdQzhZL+U8ghqBbiLOpyp5q5q2BBpFrNqWaqKJLubsw-zVL5MiB4VgVZMoBEriLoNtq2K1VHLpCVpQJ+a4qErLLbaPBArvoJb8apauZOpt0g7hMFauYlb3r8bVbdDfbshjLTLzKg6YA0qJB3aQR1bpheaZhnNwQKA-yd5SRtlo41AK6aaKLWaK7bKpSdMIA2acqrbC7PaWqRxQIkwKUIauIYkw6LqI6goaaB6NJY7sYToE68aLrVaC6XRWAi7majqSrh7LqXc8haIItNEijgR4cUo3igtgQ5AEVE7573V8pvrfqVNQtl9FyXsJJ5IxbUocJY8abEYvhKquJiccJZ6VbMhEZz657VzarfigSS8RNsb0twoQHAHWiMLl6u6cb4Hw6bUaavgyqTpOof6NJcqN6F7wRT0-zCG6L3A0GR6XdOoryB0rypL9Y7zghmgb66DX7QzFSYA0gQAMAsrfTWHGU6EeSi7ISYB5gBH0AhHXS6EVTuHeHUB+GrSyGVSZG-zcBYjDYQLgBMkqq6rv6aJqr2HkHsauGdGuJUaH8dEd5tYiFtHtCyEnod4ro16oRKHEAASQK7q36AGk7e81V6AOIi9KjASAyDTIyyafq6C-rX6r8-c7GkjzGH9ga0oRrvDT1AA-50AAp1eqQARYZABOhkABkGQhvxqATowAahVAA-BkABiGKYvolqQAewZABITUAG3jQAM8VAAwFwGMABkIwAeIZAAtBkAF0GeqCpipy47SkqnqJPV-L8MVWi66jxKAGujTGhtxlPNVQaNQSZnvAaoa1+nW6ANPAfDSTqeJrg9u8ZiAHh9APhqkE5sxjSL4LZ6ZtgWZ3Ck6euqip5n0GZpKk6LK7+biWiIgBgDutwFevAJ8CgU9QAGO1AALRUAAA5QAF8D6pimCnAAhBl2IacAHztLpi43YxGz5qZ75l535t6Juh46wO5+x9u+m461Z-GIgYpzfUoU5vxQRIu4pnRYpuu7WZmgSglsmim+AKm1HOu0FsunSxmogPltwU9QAQmt6ohrOi6pRiCnKmanejdjABpBg1p0uFfKy8CtWviIC+bshJbkrYD+cuffB2a4j2a4mMvfyOZItsfufkWQTVs7pMf5r6oGouVlr4TUqMfbBINqlVfVamMxeanqcACpFQACUVAAQt2VbDZGMAEAGAZw4wAcwY6no3mFRjOi8XradKrAvBjWs4IADXW1Mhr4DJXXqXyEzELJrJWXLEycg7ad2wOW3GASZa5aKHlbfHN8vgS25FtqjBibTAKBWazykwLHQhyZuraWSqK7XyiilmcJW6kxtqi7Dot0IAEAqQwBOpiBXRQJG7fXcGaJX7j3sxukcIh7u2oxx6d5hMmXoj1HUkiiJIqWEmHnVdPXJQtqJ2PaTHV77cz6B356SXkA4HIPVyyiy5AmoBgnC0z8L9wmJHonUnYmH5XBajVdzN6XN8YksYYHD5aJJSKXYOL74O0Ca4vgqA5NJmrA2BeCXHYAN7t7d7vN96IsiPj9EOKiqjWyVdj7y0vrInGV6B3MH6uJIGX7sOUmEyP7ExnACpgQ32JgIOaO1mKZwGUyTy-iQT8inTVnoPqPQHdPEGmqUH+2aOe2+67PLOSnDr7cX8fRXmRLyOCjGyNNvc3y44Gi-c5AsgyqA82ioWqpAAwJWqnqmmMAECGeqQAQ3NAA3uUAAHvVgbDYebDNqGT0eyBjTYA+JELtc++nRLL1pQJJzMkqqQAEzTAA0TTGOmN6J3kACuGPJwACYZ51KPpTqD6zopfON3-Ov3nZuYT6CpQv76Ep2jovYuEvkv0vMu-Oy4cvkc8vPMn7CuP5Jv5z+lsovjtZVuquKzllTWvx5n3PMhPPNTyOzzAJOxcAB1OBuAp85Rmggxfy6W4OSSiAd62c964pgRT0gwejdjApAADBm2M6MAD1fXYwATwZABZBiW6G7LgoLE-HUi7qtB4h+2Oh8ACiGQAHvj4vpjtjABLBlTa6PVb6MAGj1VYwAWKjAANaMAFUGGpjewAQ4ZABehkAGGGQASYZM3AAK40ADWogpzNhH7owAeQZuj8XMesvUfUdaJLvrvYGXtCzxuPu4tOpVPcAv7WAK6-6IBSBg2uGvudOSn6BQnsNSgcdMO37X7btV24o92D2j32Qhw3Az2NMBahad5r2Rw72YkbegxX7RHxHJPJHdQAP7LbON7MGnPAHN9XOIR16zOyjGW3Go78TTxWAP0ihggNoJOUDg-KsxHggJGQ6YA0TvzHpvz5giSInC-UmQ-S+w-y-K+tzDhjg8-egMo6+y-G-i-Q-C-jBVHIQrmbmVm4OvwE-irXHJ+2Afu4-8bxHvyvhyhCg9yYMc-9gO-dyu+NoyUk-Ln5H+GyH5g6EgA
GetPaths
.は単純split
[]と[number]はこの文字列も残してsplit
code:typescript
type Split<Str extends string, Separator extends string, Preserve = false> = Str extends ${infer Key}${Separator}${infer Rest}
?
[
...(Key extends '' ? [] : Key),
...(Preserve extends true ? [Str extends ${Key}${infer Matched}${Rest} ? Matched : Separator] : []),
...GetPaths<Rest>
]
: Str extends ''
? []
: Str
type SplitArrayPaths<KeyPaths extends string[], Separator extends string> = KeyPaths extends [infer KeyPath extends string, ...infer Rest extends string[]]
? ...Split<KeyPath, Separator, true>, ...SplitArrayPaths<Rest, Separator>
: []
type GetPaths<KeyPath extends string> = SplitArrayPaths<
SplitArrayPaths<Split<KeyPath, '.'>, '[]'>,
[${number}]
type paths = GetPaths<'foo.arr[].buz1'>
// ['foo', 'arr', '[]', 'buz', '1']
GetTypeByPath
GetPathsのkeyリストを元に、目的のパスに降りていく
code:typescript
type GetTypeByKeys<T, Paths extends string[]> = Paths extends [infer Key extends string, ...infer Rest extends string[]]
? T extends any[]
? Key extends '[]'
? GetTypeByKeys<Tnumber, Rest>
: Key extends [${infer Index extends number}]
? GetTypeByKeys<TIndex, Rest>
: never
: Key extends keyof T
? GetTypeByKeys<TKey, Rest>
: never
: T
type GetTypeByPath<T extends object, KeyPath extends ObjectKeyPaths<T>> = GetTypeByKeys<
T,
GetPaths<KeyPath>
// type TText = GetTypeByPath<typeof nestObj, 'obj.text'>
ChangeTypeByKey
GetPathsのkeyリストを元に目的のパスに降りていき、最終地点でNewTypeに差し替える
code:typescript
type ChangeTupleTypeByIndex<T extends any[], Index extends number, NewType> = T extends ...infer Rest, infer U
? Rest'length' extends Index
? ...Rest, NewType
: ...ChangeTupleTypeByIndex<Rest, Index, NewType>, U
: T
type ChangeTypeByPaths<T, Paths extends string[], NewType> = Paths extends [infer Key extends string, ...infer Rest extends string[]]
? T extends any[]
? Key extends '[]'
? Array<ChangeTypeByPaths<Tnumber, Rest, NewType>>
: Key extends [${infer Index extends number}]
? ChangeTupleTypeByIndex<
T,
Index,
ChangeTypeByPaths<TIndex, Rest, NewType>
: T
: Key extends keyof T
? {
K in keyof T: K extends Key
? ChangeTypeByPaths<TK, Rest, NewType>
: TK
}
: T
: NewType
type ChangeTypeByKey<T extends object, KeyPath extends ObjectKeyPaths<T>, NewType> = ChangeTypeByPaths<
T,
GetPaths<KeyPath>,
NewType
// type TText = ChangeTypeByKey<typeof nestObj, 'obj.objDate', Date>
transformByKey
上記のtype定義に倣って実装する。
code:typescript
const preserveSplit = (str: string, separator: string | RegExp): string[] => {
const matched = str.match(separator)
if (matched == null) {
return str ? str : []
}
const matchedText = matched0
const matchedIndex = matched.index || 0
const prevText = str.slice(0, matched.index)
return [
...(prevText ? prevText : []),
matchedText,
...preserveSplit(str.slice(matchedIndex + matchedText.length), separator)
]
}
const getPaths = (keyPath: string): string[] => {
return keyPath
.split('.')
.map((path) => preserveSplit(path, /(\\)|(\\d\)/))
.flat()
}
const transformByPaths = (obj: any, paths: string[], transformer: (value: any) => any): any => {
const path, ...restPaths = paths
if (path == null) {
return transformer(obj)
}
if (Array.isArray(obj)) {
const arr = obj
if (path === '[]') {
return arr.map((item) => transformByPaths(item, restPaths, transformer))
}
const match = path.match(/\(\d)\/)
if (match) {
const index = parseInt(match1)
return [
...arr.slice(0, index),
transformByPaths(arrindex, restPaths, transformer),
...arr.slice(index + 1)
]
}
return arr
}
const keys = Object.keys(obj)
if (keys.includes(path)) {
return {
...obj,
path: transformByPaths(objpath, restPaths, transformer)
}
}
return obj
}
const transformByKey = <
T extends object,
Key extends ObjectKeyPaths<T>,
Transformer extends (value: GetTypeByPath<T, Key>) => any
(obj: T, keyPath: Key, transformer: Transformer): ChangeTypeByKey<T, Key, ReturnType<Transformer>> => {
return transformByPaths(obj, getPaths(keyPath), transformer)
}
上記パターンだとオブジェクトから一括変換することができない
再帰しようにもRecordの時点で順不同で結果が不安定になってしまうため
code:typescript
// イメージしたもの
Object.keys(transformerSet).reduce((obj, key) => {
return transformByKey(obj, key, transformerSetkey)
}, obj)
ChangeTypeByKeyValueSet
最初からオブジェクトのものを渡して、その時に一致するkeyをvalueに差し替える。
探索方法はルートから全体を見ていき、途中までのパスがあっていないオブジェクトはそのまま返し、先頭部分がマッチしているものだけ深ぼっていく。
code:typescript
type FilterStartsWith<S extends PropertyKey, Start extends string> = S extends ${Start}${infer Rest} ? S : never
type ChangeTypeByKeyValueSetImpl<T, KeyValueSet extends Record<string, any>, CurrentPath extends string = ''> =
FilterStartsWith<keyof KeyValueSet, CurrentPath> extends never
? T
: CurrentPath extends keyof KeyValueSet
? KeyValueSetCurrentPath
: T extends any[]
? number extends T'length'
// 配列の場合
? Array<ChangeTypeByKeyValueSetImpl<Tnumber, KeyValueSet, ${CurrentPath}[]>>
// タプルの場合
: T extends ...infer Rest, infer U
? [
...ChangeTypeByKeyValueSetImpl<Rest, KeyValueSet, CurrentPath>,
ChangeTypeByKeyValueSetImpl<U, KeyValueSet, ${CurrentPath}[${Rest['length']}]>
]
: T
: keyof T extends never
? T
: {
K in keyof T: K extends string
? ChangeTypeByKeyValueSetImpl<TK, KeyValueSet, JoinObjectKey<CurrentPath, K>>
: TK
}
type ChangeTypeByKeyValueSet<T extends object, KeyValueSet extends Partial<{ K in ObjectKeyPaths<T>: any }>> =
// ObjectKeyPathsにないKeyを指定していたらエラーと気づけるようにneverを返す
Exclude<keyof KeyValueSet, ObjectKeyPaths<T>> extends never
? ChangeTypeByKeyValueSetImpl<T, KeyValueSet>
: never
type newType = ChangeTypeByKeyValueSet<typeof complecatedObj, { "objArr.arr[].objArrObjDate": Date }>
transform
上記のtype定義に倣った実装。
code:typescript
const joinObjectKey = (currentPath: string, key: string) => {
return currentPath === '' ? key : ${currentPath}.${key}
}
const transformImpl = (obj: any, transformerSet: Partial<Record<string, (value: any) => any>>, currentPath = ''): any => {
// 現在のパスがtransformerSetに含まれていない場合は変換処理を行わずそのまま返す
const filteredMatchedPaths = Object.keys(transformerSet).filter((path) => path.startsWith(currentPath))
if (filteredMatchedPaths.length <= 0) {
return obj
}
// 完全一致のtransformerがある場合は実行する
if (filteredMatchedPaths.includes(currentPath)) {
const transformer = transformerSetcurrentPath
return transformer ? transformer(obj) : obj
}
if (Array.isArray(obj)) {
const arr = obj
// 次のpathに配列指定が含まれているか
const isNextArrPath = filteredMatchedPaths.some((path) => path.startsWith(${currentPath}[]))
return arr.map((item, index) => {
// 配列指定が含まれている場合は優先的に配列指定、そうでない場合はタプル指定にする
const nextPath = isNextArrPath ? ${currentPath}[] : ${currentPath}[${index}]
return transformImpl(item, transformerSet, nextPath)
})
}
if (typeof obj === 'object') {
const keys = Object.keys(obj)
return Object.assign({}, ...keys.map((key) => ({
key: transformImpl(objkey, transformerSet, joinObjectKey(currentPath, key))
})))
}
return obj
}
type TransformedMap<TransformerSet extends Partial<Record<string, (value: any) => any>>> = {
K in keyof TransformerSet: ReturnType<Exclude<TransformerSetK, undefined>>
}
const transform = <
T extends object,
TransformerSet extends Partial<{ K in ObjectKeyPaths<T>: (value: GetTypeByPath<T, K>) => any }>
(
obj: T,
transformerSet: TransformerSet
): ChangeTypeByKeyValueSet<T, TransformedMap<TransformerSet>> => {
return transformImpl(obj, transformerSet)
}
GetTypeByKey
pathを分割しないで下っていくバージョン
code:typescript
type GetTypeByKeyImpl<T, Key extends string, CurrentPath extends string = ''> = Key extends ${CurrentPath}${infer Rest}
? Rest extends ''
// CurrentPathがKeyと一致したらTを返す
? T
: T extends any[]
? number extends T'length'
// 配列の場合
? GetTypeByKeyImpl<Tnumber, Key, ${CurrentPath}[]>
// タプルの場合
: T extends ...infer ArrRest, infer U
?
| GetTypeByKeyImpl<U, Key, ${CurrentPath}[${ArrRest['length']}]>
| GetTypeByKeyImpl<ArrRest, Key, CurrentPath>
: never
: keyof T extends never
? never
// オブジェクトの場合
: {
K in keyof T: K extends string
? GetTypeByKeyImpl<TK, Key, JoinObjectKey<CurrentPath, K>>
: never
}keyof T
: never
type GetTypeByKey<T extends object, Key extends ObjectKeyPaths<T>> = GetTypeByKeyImpl<T, Key>
/icons/hr.icon
https://www.typescriptlang.org/play?target=6#code/MYewdgzgLgBBCWBbADgGwKYHkBGArGAvDAN4BQMMYArogFwwAMANOTACYCGU69A5AEwN+AZgC0DAIyiJAFgAqQ2g2FLhAOgCcAVgBsALV4sAvqVKhIsMOmg58RMhRB56DijG4APKH0OtHeABEuHhgBITFJaXlFZVVNXQNWI2NTc2gYDgAnTNtCElYszKDuegBtMJFxKVkFflU47X1DUMFKyJqYlWV4poBdFgpC2zLXCk9vUN83DOzbYpCKiOroutjuxsSKI36C7ICQKmwMebK-GHLWpaja+vWE3h3pi-Cq686G+6YWl-aV2-UNg8BjBeqQTGZwOkoFQ0Fg8HlXNDYSdzotXh1Vl0AZ9vm1ljc1ti+hkIOdoJl4GAAOZfcmUqmPdwwjDDc5nUZucY+YHTJEswLBPiXdF-Qk9TZuZLss4Uah0Rg8znMuG4FFo34ErHi3hnEwUXok87EdzoLz0OnUgDcTNhc0FcCgFOpMGSJEoNHocuw6Ey1r5KpRFqpLtB4LSsFAKAwwGCbFy9lYTlwAEFsi4zoURjKTWbJor-CnZgKSrirhj-trdYyxsrTtMKBzeaaJrwpvWa7biwthRr3ncmtmpe3G245fRmNmO-zVfb1fi+0SJdM9W4DRxSaVjVyHU6qX7lXaS0GXV9jWP3Yhvb6bdPA476SGkmDUpDYIgfVT0HH4QmKGp-wgUYqsC-5qFYNh4CB-5DJBrCgf6thQWokawjG3Dfrgz6kFAACeyDoDAABSICUrY6DAFAADS6A4QAPAAwlQ2ToGAUAAApcAAFjAzYsWwpJBl8ybIPhYBsNROE8V4fECfe1IAHyEKwjHMaxHFQNxvFiaSrZnAA-DAwmieJNFnPQAAGAAkxAqZkLHsVxRhqNZRl8RJRjmaYuH4TAZEURJ6mcRAtFyFJ3DaTASbkVAXy2fZgVhTJO4PkQraKUQoVafxGRgDhpSghQBlej6iURXI5QYNSGkPGcAD0tUwIAsomAOhKgB2DIALBqABAq+kwJl0kRQAFJSABmJUAKoAJT5dmemTjAAA+MBWTZTF2Wpjn5Z57YUItA1jaV2VRRRc2Fb5eDRQFXHBWNXzLXF60aUYm3ySdMCeugABuPpzRN2bvV9mR1Q1gD9DIA6wyANcMHXddM9B9eF2WlKBI0lQAStYMUwMjmQwGNBX1rN20LUt1n3Q5j2bUTd2rfFG3WWj0AVSxVLVb02xbdtu37VlpJHVAr0GX5VE0YF123ST1MPZxT29OZlPi6pZNS6UdPo4zVWcQ8bMvYTFD-d9hO-YTi2C5dGnBfTGOk4F2v1nrgO6-kTyUZjYAwAA1jRIDDb1vT0DAzvc8l1IzXNi3EaR53+TRDES4rXyUTb7a7eVlEGoHvP82duAXcLV0haUqdfOHYAm9HVtcfH8mJ9tds-X9lCffrLqlB7OFez7Xl4QRADi6BQHIXcAEI4RJACSUYhfHNEHbJu6xbHCWB8eqW8Ol-vT4HVMK4FRjWVjMAWx5rAGRbM+TEDMDlxpgAyDBJgAWDIAAHKAC+BgDqDIA+gyAJEMciAEkMgAr8YAmgw9TkGZXqZ8OC5Wmu2IqNArxn3KrwSqzMNZ43bPVJqbUupzQMr3fuQ8R40XHmgfOxVMj9HXjhMWK1t4bRltXaYaCwaQ0wYTWGZ9Eb-n3qmTIFsvj71xvzV6O0YA4IHvhYeY8J43XIZQq+SsXLZAtmrJBmtaGCKJiIvBEiiFcJ4dIy+C8uJ0Ntg3AG9dW7tzhklKwpjCZFUbvbbaaDABVDIANYZAAdDIAcoZAD1DIACYYoZzXTDrc4ztKTu09t7OQvt-ZnyDGo7BfdRHoHEQQieKcyESSLiREukchZ0VkZXIx7Za6EyeuYiJeNa7YS7sIhJmjo6WIirzKeklA6lxwiLEKVc8gaLEfgnChDUCT3IS9KpPkABi8BUDcEyAAZSgFkKAEAADq8ANK0RmWfNimQQD4UyLhDJMA5kLJiXJKka8Nmb2skcvZu9iD70PrLAyGzKneQInZdIRAJlTJ9NcxZKy1m8A4EC3gRNAXYGwCCxavBwUQq+GC1encfL0U4uAz8iTkk4QAGocFQFQdAMy+4DKGRJbFuL8V9zPmjUAmQ2C0UEjlHC8l57UI0ic3ceQ0pKQoF86ZvzlmrM4rRMp5DSV4oJZbAxGlFKB2sU3U6wCYb6JZZpfq2VhUkpxWKvuM0RWavJVAUosiUFuFYYHcBeVjXTGgZeEqgd4GIJZq9NBLV-FBIMlwjgeSUXUnQOivpor9VEvKiQ9JNEA3ipkZKpWtDCluAYRDV1OtTWqo3EjMAo1sa6L4Za7aBlShqOmKBZFqLfV1KxXq8VRLdEarJRGpVa1FZMoLW4YtPq-U1q1VAIlUiO36sjcqp6KsGYIKZizLWzac1FN6gEsJbcIln1lQ42x06gmBKCcEl2s6LFRIDimoOVJm0GVbWist4bCWpILqG8tta+6ZIjtnKOeSo0FObbDS9r1wSvMvt6k9vTe3ipCmfJpuqb2wEDhxPZ8AcW0WNAXTdbSOlyHklE81LoukEFYGghDV1ADWDIAKwZAAiDBJb+gBwY0AFnaL9ABmDARz+gAKhkAJcMgAfhjvoAdBtACmDIARQZADRDIACIZABiDDhxdf9AEUAAKIeGALitg6AhXhJA52r42GzadOlXuxdPVj2lr-WGit56iFyGaWeqA1cXnVKsAAd0SXkTT7adOgdoq89uKFoyxkQm6AAREmLhahCj5TUF5osM5uDufoPMNDiKCIBu6bU7TdFHPe3fJkT8GE4WFH2IcY4wR8oItGW86wVApl5CU0FBzXd24wVwCM8MMBcBZLaXkAawAo3mlOV8VuLXdwTUIIpVwdloSZFdk15VhACArxgAZVub1ibECGw2nezliCtyPmGV87hMjgIgMNEAmREADIa0meg5qviOg21tnbPy+70Ag1AKDgyqXbdpfSgaH09WHdyl1ggilzVVy+LNmmrKV4TTe5JT7jsYBoMAH-OgAKdVaoARYZACdDNfE7kAzuJfFThwA1CqAD8GQAMQzUcI11QA9gyAEhNQA28aADPFQAYC7f0ADIRgB4hkAFoMgBdBlaljrHADWDVeGpM6ZX4ACyXBgCcS-CLIrOS1CtwgANZHm3tto77hNNQ3PvmZAGgNZAXEPuKQ1xpNQ0AFn8o0o1qNE1DYwDSCADAahUAgCpMb5VXxle87YALqAQuRdXTN-Ab2A0nc+n54L4XbARbW9HdxWiRAGBdY5H1pirskxPgoGgwAMdqAAtFZ+rUZeo59NfQAQgxcaJ4AfO0af-y46wb3MBfc8-9y7wPHuzZqEpJJqg0mpd-clqbsHFBqtZ7lyVIgvfzuzL7qUdvitjWx4G2t07ffsYGUH4lgaSYuv0AT1sUwFAK8DQ9ThRvEAd9L7wJ3jk1XCh5DX3GhqgBCa1ajrziOGWrkevtjvHBGuOAGkGM41X4AQAAHLNi4QSiID9zslrzdyDxDwgBAHfDVzvy1xgDvz13mT2UN04gGmWjHx3k2lNzOEn1dkKDUEQA4GQDV1WXQEQF4TElNHgJHGdWaifxf2owL06kJ0ACpFQACUVAAQtwf3oLI0AEAGJnPjQAcwYCcWDGFyMcNS9sxqsrAvAgDMY-8ADsgEoDIMCo1pZZYLJrJMDaY7kqCPA2Zsw8Dp8Uc5cBkhpuAKCTDZch861ZDFYzctgzdwRN8fd4tIofxRtQheZeBo8v9VtJcxcH0oAJcaIpdl9cC+448s5oofMIAEAqQwABpiBXRQJJdCDiC1dW54DkjswW4aIokF9zCkx8icIyEF8LsMZat70c4cJ7c5sK5Z0cDlxTdnDWBjCE9P1qk5B1tTDzta9kAQpeibD5cwM91rtbtaJ7saU6VWtK8XsyVgd4Dvt0Mwc4NQlhUeiZ9bC+4ok0Z+swBElaJxNm9pMhjtjRjL0vgqAqDucrA2Aq4sIe9hjUc8haJWAGlDoclgQti+jRjNkFlJjYMQlXZitgokMolntXsalcFYtiV5Jljcpwt5IBpExnBepgQKjh8JhfiRjKjSAgdv0S1bNr1O0hlcTs8Bjzi-jKj0Met2ioip8iioxD9cBjsXjZ9xVfoujxlq9MhXd3c2AjjPieZvjyFSRWkclTYSskMvg5AshPxYT0B0pMMGpAAwJUalajfkAECGVqQAQ3NAA3uUAAHvVgCSCUvdM0nqHpJJPpYlMI1TeGUkeUpLGLAiAyM0qbdTRVdTUgNBQAEzTAA0TXfgI1bkACuGeHHxXLGAE4qTLTGTEUjw4I5pc0x0mIx9RDJlXqBU10lUpPdUzUnU-U4000sIs+S0q1GEkku0nCCAB0pKZ0xUqzOxAGKbcsk1Exb6KMkA+vIKPIHlH0AUoPI49wwCTsNk0ITgbgSFUIOUZoIMKrVbBfMLIgd4igBMppVU-dfDLjQKQAAwYOMcNAA9Xy40AE8GQAWQYSyayz4d9aIwSVNgQ0Egxty9yONDzAAohkAB747Ut+DjQASwY+CwTMdcdCNABo9TvkAFiowADWjABVBjxwX0AEOGQAXoZABhhkAEmGIQwACuNAA1qOviELPLw0AHkGPDMvPM8U687IT1Wifs-kuvIUruIZO82U-dR4lEtE3AWGYESXegS0mgznRcjknYiYaY2lM00oENL4KExYhleA+YNeQWOIhIpI9kIcNwNIsIjIkggabI7rSvEcUoyEhYvFDrekeAizGAeYSSvFRwl0ZomAM3YwhfVk9ki4yo7kl8Cwawyk8VPIRyirU8VgQFPYLLXoXgegSy9AUy9Aczcy4IcKiaYEdzCrPzCreYELeYvVSK6KiywyiKhKtLA4I4dAeYfKfKNKuKnSsy7KjK4wX6CESAS3dAa3W3aXAS98NgLk9yqEVqr8ZczyuXCyvy1ENLYK5oQKooAqzLbgEqkK3oX6C3K3G3O3CotgeYX6IAA
汎用的なtransformから日付専用のtransformに切り出すのは型都合上難しそう
どうしてもやるなら始めから日付に特化したtypeとかにしておかないと推論で死ぬ
https://www.typescriptlang.org/play?target=6#code/MYewdgzgLgBBCWBbADgGwKYHkBGArGAvDAN4BQMMYArogFwwAMANOTACYCGU69A5AEwN+AZgC0DAIyiJAFgAqQ2g2FLhAOgCcAVgBsALV4sAvqVKhIsMOmg58RMhRB56DijG4APKH0OtHeABEuHhgBITFJaXlFZVVNXQNWI2NTc2gYDgAnTNtCElYszKDuegBtMJFxKVkFflU47X1DUMFKyJqYlWV4poBdFgpC2zLXCk9vUN83DOzbYpCKiOroutjuxsSKI36C7ICQKmwMebK-GHLWpaja+vWE3h3pi-Cq686G+6YWl-aV2-UNg8BjBeqQTGZwOkoFQ0Fg8HlXNDYSdzotXh1Vl0AZ9vm1ljc1ti+hkIOdoJl4GAAOZfcmUqmPdwwjDDc5nUZucY+YHTJEswLBPiXdF-Qk9TZuZLss4Uah0Rg8znMuG4FFo34ErHi3hnEwUXok87EdzoLz0OnUgDcTNhc0FcCgFOpMGSJEoNHocuw6Ey1r5KpRFqpLtB4LSsFAKAwwGCbFy9lYTlwAEFsi4zoURjKTWbJor-CnZgKSrirhj-trdYyxsrTtMKBzeaaJrwpvWa7biwthRr3ncmtmpe3G245fRmNmO-zVfb1fi+0SJdM9W4DRxSaVjVyHU6qX7lXaS0GXV9jWP3Yhvb6bdPA476SGkmDUpDYIgfVT0HH4QmKGp-wgUYqsC-5qFYNh4CB-5DJBrCgf6thQWokawjG3Dfrgz6kFAACeyDoDAABSICUrY6DAFAADS6A4QAPAAwlQ2ToGAUAAApcAAFjAzYsWwpJBl8ybIPhYBsNROE8V4fECfe1IAHyEKwjHMaxHFQNxvFiaSrZnAA-DAwmieJNFnPQAAGAAkxAqZkLHsVxRhqNZRl8RJRjmaYuH4TAZEURJ6mcRAtFyFJ3DaTASbkVAXy2fZgVhTJO4PkQraKUQoVafxGRgDhpSghQBlej6iURXI5QYNSGkPGcAD0tUwIAsomAOhKgB2DIALBqABAq+kwJl0kRQAFJSABmJUAKoAJT5dmemTjAAA+MBWTZTF2Wpjn5Z57YUItA1jaV2VRRRc2Fb5eDRQFXHBWNXzLXF60aUYm3ySdMCeugABuPpzRN2bvV9mR1Q1gD9DIA6wyANcMHXddM9B9eF2WlKBI0lQAStYMUwMjmQwGNBX1rN20LUt1n3Q5j2bUTd2rfFG3WWj0AVSxVLVb02xbdtu37VlpJHVAr0GX5VE0YF123ST1MPZxT29OZlPi6pZNS6UdPo4zVWcQ8bMvYTFD-d9hO-YTi2C5dGnBfTGOk4F2v1nrgO6-kTyUZjYAwAA1jRIDDb1vT0DAzvc8l1IzXNi3EaR53+TRDES4rXyUTb7a7eVlEGoHvP82duAXcLV0haUqdfOHYAm9HVtcfH8mJ9tds-X9lCffrLqlB7OFez7Xl4QRADi6BQHIXcAEI4RJACSUYhfHNEHbJu6xbHCWB8eqW8Ol-vT4HVMK4FRjWVjMAWx5rAGRbM+TEDMDlxpgAyDBJgAWDIAAHKAC+BgDqDIA+gyAJEMciAEkMgAr8YAmgw9TkGZXqZ8OC5Wmu2IqNArxn3KrwSqzMNZ43bPVJqbUupzQMr3fuQ8R40XHmgfOxVMj9HXjhMWK1t4bRltXaYaCwaQ0wYTWGZ9Eb-n3qmTIFsvj71xvzV6O0YA4IHvhYeY8J43XIZQq+SsXLZAtmrJBmtaGCKJiIvBEiiFcJ4dIy+C8uJ0Ntg3AG9dW7tzhklKwpjCZFUbvbbaaDABVDIANYZAAdDIAcoZAD1DIACYYoZzXTDrc4ztKTu09t7OQvt-ZnyDGo7BfdRHoHEQQieKcyESSLiREukchZ0VkZXIx7Za6EyeuYiJeNa7YS7sIhJmjo6WIirzKeklA6lxwiLEKVc8gaLEfgnChDUCT3IS9KpPkABi8BUDcEyAAZSgFkKAEAADq8ANK0RmWfNimQQD4UyLhDJMA5kLJiXJKka8Nmb2skcvZu9iD70PrLAyGzKneQInZdIRAJlTJ9NcxZKy1m8A4EC3gRNAXYGwCCxavBwUQq+GC1encfL0U4uAz8iTkk4QAGocFQFQdAMy+4DKGRJbFuL8V9zPmjUAmQ2C0UEjlHC8l57UI0ic3ceQ0pKQoF86ZvzlmrM4rRMp5DSV4oJZbAxGlFKB2sU3U6wCYb6JZZpfq2VhUkpxWKvuM0RWavJVAUosiUFuFYYHcBeVjXTGgZeEqgd4GIJZq9NBLV-FBIMlwjgeSUXUnQOivpor9VEvKiQ9JNEA3ipkZKpWtDCluAYRDV1OtTWqo3EjMAo1sa6L4Za7aBlShqOmKBZFqLfV1KxXq8VRLdEarJRGpVa1FZMoLW4YtPq-U1q1VAIlUiO36sjcqp6KsGYIKZizLWzac1FN6gEsJbcIln1lQ42x06gmBKCcEl2s6LFRIDimoOVJm0GVbWist4bCWpILqG8tta+6ZIjtnKOeSo0FObbDS9r1wSvMvt6k9vTe3ipCmfJpuqb2wEDhxPZ8AcW0WNAXTdbSOlyHklE81LoukEFYGghDV1ADWDIAKwZAAiDBJb+gBwY0AFnaL9ABmDARz+gAKhkAJcMgAfhjvoAdBtACmDIARQZADRDIACIZABiDDhxdf9AEUAAKIeGALitg6AhXhJA52r42GzadOlXuxdPVj2lr-WGit56iFyGaWeqA1cXnVKsAAd0SXkTT7adOgdoq89uKFoyxkQm6AAREmLhahCj5TUF5osM5uDufoPMNDiKCIBu6bU7TdFHPe3fJkT8GE4WFH2IcY4wR8oItGW86wVApl5CU0FBzXd24wVwCM8MMBcBZLaXkAawAo3mlOV8VuLXdwTUIIpVwdloSZFdk15VhACArxgAZVub1ibECGw2nezliCtyPmGV87hMjgIgMNEAmREADIa0meg5qviOg21tnbPy+70Ag1AKDgyqXbdpfSgaH09WHdyl1ggilzVVy+LNmmrKV4TTe5JT7jsYBoMAH-OgAKdVaoARYZACdDNfE7kAzuJfFThwA1CqAD8GQAMQzUcI11QA9gyAEhNQA28aADPFQAYC7f0ADIRgB4hkAFoMgBdBlaljrHADWDVeGpM6ZX4ACyXBgCcS-CLIrOS1CtwgANZHm3tto77hNNQ3PvmZAGgNZAXEPuKQ1xpNQ0AFn8o0o1qNE1DYwDSCADAahUAgCpMb5VXxle87YALqAQuRdXTN-Ab2A0nc+n54L4XbARbW9HdxWiRAGBdY5H1pirskxPgoGgwAMdqAAtFZ+rUZeo59NfQAQgxcaJ4AfO0af-y46wb3MBfc8-9y7wPHuzZqEpJJqg0mpd-clqbsHFBqtZ7lyVIgvfzuzL7qUdvitjWx4G2t07ffsYGUH4lgaSYuv0AT1sUwFAK8DQ9ThRvEAd9L7wJ3jk1XCh5DX3GhqgBCa1ajrziOGWrkevtjvHBGuOAGkGM41X4AQAAHLNi4QSiID9zslrzdyDxDwgBAHfDVzvy1xgDvz13mT2UN04gGmWjHx3k2lNzOEn1dkKDUEQA4GQDV1WXQEQF4TElNHgJHGdWaifxf2owL06kJ0ACpFQACUVAAQtwf3oLI0AEAGJnPjQAcwYCcWDGFyMcNS9sxqsrAvAgDMY-8ADsgEoDIMCo1pZZYLJrJMDaY7kqCPA2Zsw8Dp8Uc5cBkhpuAKCTDZch861ZDFYzctgzdwRN8fd4tIofxRtQheZeBo8v9VtJcxcH0oAJcaIpdl9cC+448s5oofMIAEAqQwABpiBXRQJJdCDiC1dW54DkjswW4aIokF9zCkx8icIyEF8LsMZat70c4cJ7c5sK5Z0cDlxTdnDWBjCE9P1qk5B1tTDzta9kAQpeibD5cwM91rtbtaJ7saU6VWtK8XsyVgd4Dvt0Mwc4NQlhUeiZ9bC+4ok0Z+swBElaJxNm9pMhjtjRjL0vgqAqDucrA2Aq4sIe9hjUc8haJWAGlDoclgQti+jRjNkFlJjYMQlXZitgokMolntXsalcFYtiV5Jljcpwt5IBpExnBepgQKjh8JhfiRjKjSAgdv0S1bNr1O0hlcTs8Bjzi-jKj0Met2ioip8iioxD9cBjsXjZ9xVfoVsLBrDKTxU8gF8BoKtTxWBAU9gsteheB6AoSyV4CLMYB5hZS8UJpgR3MKs-MKt5gQt5i9V5T0BzNFTghlT0BVTWB1S9gDgjh0B5h8p8odSTT9TDSlSFiVTjBfoIRIBLd0BrdbdpcOT+iuTSAgA
#TypeScript