零、寫在前面
如果沒有 TypeScript
和 裝飾器
,純 JavaScript
有沒有什麼好的資料轉換的方案呢?
很遺憾,還真有,雖然沒有 TypeScript
那麼優雅,但是也足夠好用。
這裏用到了 Getter
/Setter
,以及 Object 原型鏈相關的知識。
一、假設需求
1. 後端返回的資料
這裏我們先假設從後端來了個 JSON
長這樣:
{ id: 1, nickname: 'Hamm', age: 18, sex: 1, createTime: 1707021296000, bio: '一些廢話,前端不需要的欄位' }
其中,
id
createTime
是固定返回的公共屬性。
2. 前端型別宣告
基類
class BaseEntity { id; createTime; }
使用者類
class User extends BaseEntity { nickname; age; sex; }
3. 轉換要求
轉換到類時
createTime
轉為 Date型別;前端使用
gender
作為性別欄位,且需要根據1/0
顯示男女;前端沒有
bio
欄位,需要過濾掉。
轉換到 JSON 時
createTime
轉回後端需要的時間戳gender
還原回後段需要的sex
,並且轉換為1/0
二、實現思路
我們可以用 getter
/setter
來攔截資料達到轉換資料的目的:
1. 基於基類實現 fromJson
靜態方法
class BaseEntity { id; createTime; static fromJson(json) { const user = new this(); const filteredJson = Object.keys(json).reduce((item, key) => { if (user.hasOwnProperty(key)) { item[key] = json[key]; } if (user.__proto__.hasOwnProperty(key)) { item[key] = json[key]; } return item; }, {}); const entity = Object.assign(user, filteredJson); if (entity.createTime) { entity.createTime = new Date(entity.createTime); } return entity; } } class UserEntity extends BaseEntity { nickname; age; gender; get sex() { return this.gender; } set sex(value) { if (value === undefined || value === null) { this.gender = undefined }else{ this.gender = value === 1 ? '男' : '女'; } } } const json = { id: 1, nickname: 'Hamm', age: 18, sex: 1, createTime: 1707021296000, bio: '一些廢話,前端不需要的欄位' } console.log("json", json) const user = UserEntity.fromJson(json) console.log("entity", user)
其中,我們透過讀取 getter
/setter
以及本身的屬性,來確定哪些是直接賦值,哪些是走set方法,哪些不存在的需要忽略。
2.除錯輸出,美滋滋:
json { id: 1, nickname: 'Hamm', age: 18, sex: 1, createTime: 1707021296000, bio: '一些廢話,前端不需要的欄位' } entity UserEntity { id: 1, createTime: 2024-02-04T04:34:56.000Z, nickname: 'Hamm', age: 18, gender: '男' }
3. 實現動態方法的 toJson
接下來,我們需要將 BaseEntity
新增一個 toJson
方法,用於將實體轉換為 JSON 格式,且給 UserEntity
的 sex getter
做一下資料轉換:
class BaseEntity { id; createTime; static fromJson(json) { const user = new this(); const filteredJson = Object.keys(json).reduce((item, key) => { if (user.hasOwnProperty(key)) { item[key] = json[key]; } if (user.__proto__.hasOwnProperty(key)) { item[key] = json[key]; } return item; }, {}); const entity = Object.assign(user, filteredJson); if (entity.createTime) { entity.createTime = new Date(entity.createTime); } return entity; } toJson() { const proto = Object.getPrototypeOf(this); const ownProperties = Object.getOwnPropertyNames(this); const getters = Object.getOwnPropertyNames(proto).filter(key => { const descriptor = Object.getOwnPropertyDescriptor(proto, key); return descriptor && typeof descriptor.get === 'function'; }); const json = {} getters.forEach(key => { if (this.__proto__.hasOwnProperty(key)) { json[key] = this[key] this[key] = undefined } }) ownProperties.forEach(key => { if (this.hasOwnProperty(key)) { json[key] = this[key] this[key] = undefined } }) if (json.createTime && typeof json.createTime === 'object') { json.createTime = json.createTime.valueOf() } else { json.createTime = undefined } return JSON.parse(JSON.stringify(json)) } } class UserEntity extends BaseEntity { nickname; age; gender; get sex() { if (this.gender === undefined || this.gender === null) { return undefined } return this.gender === '男' ? 1 : 0; } set sex(value) { if (value === undefined || value === null) { this.gender = undefined }else{ this.gender = value === 1 ? '男' : '女'; } } } const user = new UserEntity() user.id = 1 user.nickname = "Hamm" user.age = 18 user.gender = "男" user.createTime = new Date() console.log("entity", user) console.log("json", user.toJson())
4. 繼續除錯輸出,繼續美滋滋
entity UserEntity { id: 1, createTime: 2024-10-23T19:37:07.521Z, nickname: 'Hamm', age: 18, gender: '男' } json { sex: 1, id: 1, createTime: 1729712227521, nickname: 'Hamm', age: 18 }
三、完整程式碼如下
class BaseEntity { id; createTime; static fromJson(json) { const user = new this(); const filteredJson = Object.keys(json).reduce((item, key) => { if (user.hasOwnProperty(key)) { item[key] = json[key]; } if (user.__proto__.hasOwnProperty(key)) { item[key] = json[key]; } return item; }, {}); const entity = Object.assign(user, filteredJson); if (entity.createTime) { entity.createTime = new Date(entity.createTime); } return entity; } toJson() { const proto = Object.getPrototypeOf(this); const ownProperties = Object.getOwnPropertyNames(this); const getters = Object.getOwnPropertyNames(proto).filter(key => { const descriptor = Object.getOwnPropertyDescriptor(proto, key); return descriptor && typeof descriptor.get === 'function'; }); const json = {} getters.forEach(key => { if (this.__proto__.hasOwnProperty(key)) { json[key] = this[key] this[key] = undefined } }) ownProperties.forEach(key => { if (this.hasOwnProperty(key)) { json[key] = this[key] this[key] = undefined } }) if (json.createTime && typeof json.createTime === 'object') { json.createTime = json.createTime.valueOf() } else { json.createTime = undefined } return JSON.parse(JSON.stringify(json)) } } class UserEntity extends BaseEntity { nickname; age; gender; get sex() { if (this.gender === undefined || this.gender === null) { return undefined } return this.gender === '男' ? 1 : 0; } set sex(value) { if (value === undefined || value === null) { this.gender = undefined }else{ this.gender = value === 1 ? '男' : '女'; } } } const json = { id: 1, nickname: 'Hamm', age: 18, sex: 1, createTime: 1707021296000, bio: '一些廢話,前端不需要的欄位' } console.log("json",json) const user = UserEntity.fromJson(json) console.log("entity", user) console.log("json", user.toJson())
四、總結
如上程式碼,我們利用了 getter/setter
以及 Object
原型鏈上的一些方法來完成了轉換。
雖然稍顯麻煩,但也算解決了不在外部寫一些方法來轉換。
各有所長,實現的方式有很多種,但思路纔是很好玩的東西。
當然,JavaScript的裝飾器提案已經快釋出了,後續我們將繼續摸索用裝飾器在JavaScript中實現類似功能。(TypeScript的裝飾器我們敢用,是因為編譯後的JS程式碼沒有包含裝飾器的部分,在瀏覽器上不會有問題。)