切換語言為:簡體

用JavaScript也能愉快的完成資料轉換

  • 爱糖宝
  • 2024-10-24
  • 2049
  • 0
  • 0

零、寫在前面

如果沒有 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. 轉換要求

  1. 轉換到類時

  • createTime 轉為 Date型別;

  • 前端使用 gender 作為性別欄位,且需要根據 1/0 顯示男女;

  • 前端沒有 bio 欄位,需要過濾掉。

  1. 轉換到 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 格式,且給 UserEntitysex 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程式碼沒有包含裝飾器的部分,在瀏覽器上不會有問題。)


0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.