在前后端的数据协议(主要指httpwebsocket)的问题上,如果前期沟通好了,那么数据协议上问题会很好解决,前后端商议一种都可以接受的格式即可。但是如果接入的是老系统、第三方系统,或者由于某些奇怪的需求(如为了节省流量,json 数据使用单字母作为key值,或者对某一段数据进行了加密),这些情况下就无法商议,需要在前端做数据转换,如果不转换,那么奔放的数据格式可读性差,也会造成项目难以维护。

这也正是我在项目种遇到的问题,网上也找了一些方案,要么过于复杂,要么有些功能不能很好的支持,于是有了这个工具[class-converter](https://www.npmjs.com/package/class-converter)。

下面我们用例子来说明下:

面对如下的Server返回的一个用户user数据:

{
"i": 1234,
"n": "name",
"a": "1a2b3c4d5e6f7a8b"
}

或者这个样的:

{
"user_id": 1234,
"user_name": "name",
"u_avatar": "1a2b3c4d5e6f7a8b"
}

数据里的 avatar 字段在使用时,可能需要拼接成一个 url,例如 https://xxx.cdn.com/1a2b3c4d5e6f7a8b.png

当然可以直接这么做:

const json = {
"i": 1234,
"n": "name",
"a": "1a2b3c4d5e6f7a8b",
};
const data = {};
const keyMap = {
i: 'id',
n: 'name',
a: 'avatar',
}
Object.entries(json).forEach(([key, value]) => {
data[keyMap[key]] = value;
});
// data = { id: 1234, name: 'name', avatar: '1a2b3c4d5e6f7a8b' }

然后我们进一步就可以把这个抽象成一个方法,像下面这个样:

const jsonConverter = (json, keyMap) => {
const data = {};
Object.entries(json).forEach(([key, value]) => {
data[keyMap[key]] = value;
});
return data;
}

如果这个数据扩展了,添加了教育信息,user 数据结构看起来这个样:

{
"i": 1234,
"n": "name",
"a": "1a2b3c4d5e6f7a8b",
"edu": {
"u": "South China Normal University",
"ea": 1
}
}

此时的 jsonConverter 方法已经无法正确转换 edu 字段的数据,需要做一些修改:

const json = {
"i": 1234,
"n": "name",
"a": "1a2b3c4d5e6f7a8b",
"edu": {
"u": "South China Normal University",
"ea": 1
}
};
const data = {};
const keyMap = {
i: 'id',
n: 'name',
a: 'avatar',
edu: {
key: 'education',
keyMap: {
u: 'universityName',
ea: 'attainment'
}
},
}

随着数据复杂度的上升,keyMap 数据结构会变成一个臃肿的配置文件,此外 jsonConverter 方法会越来越复杂,以至于后面同样难以维护。但是转换后的数据格式,对于项目来说,数据的可读性是很高的。所以,这个转换必须做,但是方式可以更优雅一点。

写这个工具的初衷也是为了更优雅的进行数据转换。

工具用法

还是上面的例子(这里使用typescript写法):

import { toClass, property } from 'class-converter';
// 待解析的数据
const json = {
"i": 1234,
"n": "name",
"a": "1a2b3c4d5e6f7a8b",
};
class User {
@property('i')
id: number; @property('n')
name: string; @property('a')
avatar: string;
}
const userIns = toClass(json, User);

你可以轻而易举的获得下面的数据:

// userIns 是 User 的一个实例
const userIns = {
id: 1234,
name: 'name',
avatar: '1a2b3c4d5e6f7a8b',
}
userIns instanceof User // true

Json 类既是文档又是类似于上文说的与keyMap类似的配置文件,并且可以反向使用。

import { toPlain } from 'class-converter';
const user = toPlain(userIns, User);
// user 数据结构
{
i: 1234,
n: 'name',
a: '1a2b3c4d5e6f7a8b',
};

这是一个最简单的例子,我们来一个复杂的数据结构:

{
"i": 10000,
"n": "name",
"user": {
"i": 20000,
"n": "name1",
"email": "zqczqc",
// {"i":1111,"n":"department"}
"d": "eyJpIjoxMTExLCJuIjoiZGVwYXJ0bWVudCJ9",
"edu": [
{
"i": 1111,
"sn": "szzx"
},
{
"i": 2222,
"sn": "scnu"
},
{
"i": 3333
}
]
}
}

这是后端返回的一个叫package的json对象,字段意义在文档中这么解释:

  • i:package 的 id
  • n:package 的名字
  • user:package 的所有者,一个用户
    • i:用户 id
    • n:用户名称
    • email:用户email,但是只有邮箱前缀
    • d:用户的所在部门,使用了base64编码了一个json字符串
      • i:部门 id
      • n:部门名称
    • edu:用户的教育信息,数组格式
      • i:学校 id
      • sn:学校名称

我们的期望是将这一段数据解析成,不看文档也能读懂的一个json对象,首先我们经过分析得出上面一共有4类实体对象:package、用户信息、部门信息、教育信息。

下面是代码实现:

import {
toClass, property, array, defaultVal,
beforeDeserialize, deserialize, optional
} from 'class-converter';
// 教育信息
class Education {
@property('i')
id: number; // 提供一个默认值
@defaultVal('unknow')
@prperty('sn')
schoolName: string;
}
// 部门信息
class Department {
@property('i')
id: number; @prperty('n')
name: string;
}
// 用户信息
class User {
@property('i')
id: number;
@property('n')
name: string; // 保留一份邮箱前缀数据
@optional()
@property()
emailPrefix: string; @optional()
// 这里希望自动把后缀加上去
@deserialize(val => `${val}@xxx.com`)
@property()
email: string; @beforeDeserialize(val => JSON.parse(atob(val)))
@typed(Department)
@property('d')
department: Department; @array()
@typed(Education)
@property('edu')
educations: Education[];
}
// package
class Package {
@property('i')
id: number; @property('n')
name: string; @property('user', User)
owner: User;

数据已经定义完毕,这时只要我们执行toClass方法就可以得到我们想要的数据格式:

{
id: 10000,
name: 'name',
owner: {
id: 20000,
name: 'name1',
emailPrefix: 'zqczqc',
email: "zqczqc@xxx.com",
department: {
id: 1111,
name: 'department'
},
educations: [
{
id: 1111,
schoolName: 'szzx'
},
{
id: 2222,
schoolName: 'scnu'
},
{
id: 3333,
schoolName: 'unknow'
}
]
}
}

上面这一份数据,相比后端返回的数据格式,可读性大大提升。这里的用法出现了@deserialize@beforeDeserialize@yped的装饰器,这里对这几个装饰器是管道方式调用的(前一个的输出一个的输入),这里做一个解释:

  • beforeDeserialize 第一个参数可以最早拿到当前属性值,这里可以做一些解码操作
  • typed这个是转换的类型,入参是一个类,相当于自动调用toClass,并且调动时的第一个参数是beforeDeserialize的返回值或者当前属性值(如果没有@beforeDeserialize装饰器)。如果使用了@array装饰器,则会对每一项数组元素都执行这个转换
  • deserialize这个装饰器是最后执行的,第一个参数是beforeDeserialize返回值,@typed返回值,或者当前属性值(如果前面两个装饰器都没设置的话)。在这个装饰器里可以做一些数据订正的操作

这三个装饰器是在执行toClass时才会调用的,同样的,当调用toPlain时也会有对应的装饰器@serialize@fterSerialize,结合@typed进行一个相反的过程。下面将这两个转换过程的流程绘制出来。

调用 toClass的过程:

调用 toPlain的过程是调用 toClass的逆过程,但是有些许不一样,有一个注意点就是:在调用 toClass时允许出现一对多的情况,就是一个属性可以派生出多个属性,所以调用调用 toPlain时需要使用 @serializeTarget来标记使用哪一个值作为逆过程的原始值,具体用法可以参考文档。

一个 json 转换工具的更多相关文章

  1. Json转换工具

    import java.util.List; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterx ...

  2. Json转换工具类(基于google的Gson和阿里的fastjson)

    在项目之中我们经常会涉及到字符串和各种对象的转换,为此特地整理了一下常用的转换方法 一.基于com.google.code.gson封装的json转换工具类 1. 在pom.xml文件里面引入gson ...

  3. java json转换工具类

    在java项目中,通常会用到json类型的转换,常常需要对 json字符串和对象进行相互转换. 在制作自定义的json转换类之前,先引入以下依赖 <!--json相关工具--><de ...

  4. AEM上的一个图片转换工具

    目的: 不同情况下,同样一张图片,需要不一样大小/背景/尺寸显示. 例子: dam下面有一张940 x 300 的图片: http://localhost:4502/content/dam/geome ...

  5. SpringMVC整合FastJson:用"最快的json转换工具"替换SpringMVC的默认json转换

    2017年11月23日 09:18:03 阅读数:306 一.环境说明 Windows 10 1709 Spring 4.3.12.RELEASE FastJson 1.2.40 IDEA 2017. ...

  6. json转换工具类:json<===>list或者对象

    public class JsonTools { /** * POJO 转 JSON */ public static String createJsonString(Object object) { ...

  7. json转换工具类

    using System;using System.Collections.Generic;using System.Text;using Newtonsoft.Json;using System.I ...

  8. json转换工具——fastjson的使用

    1.maven依赖<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson< ...

  9. java util - json转换工具 gson

    需要 gson-2.7.jar 包 package cn.java.gson; import com.google.gson.JsonElement; import com.google.gson.J ...

随机推荐

  1. HTML data-* 属性的含义和使用

      data-*自定义数据属性 首先讲一下语法格式: data-* =“值” data-* 属性包括两部分: 属性名不应该包含任何大写字母,并且在前缀 "data-" 之后必须有至 ...

  2. 网络流 + 欧拉回路 = B - Sightseeing tour POJ - 1637

    B - Sightseeing tour POJ - 1637 https://blog.csdn.net/qq_36551189/article/details/80905345 首先要了解一下欧拉 ...

  3. HTML往div中赋值

    HTML中往div赋值 <div id="mazey">content</div> 使用JavaScript: var mazey=document.get ...

  4. 一篇文章解决MongoDB的所有问题

    目录 一.MongoDB相关概念 1.1 业务应用场景 1.1.1 而MongoDB可应对"三高"需求· 1.1.2 什么时候选择MongoDB? 1.1.3 如果用mysql? ...

  5. 记录:通过ffmpeg rtsp转 http m3u8

    环境 Windows 10 大华rtsp直播 转 http请求m3u8 ffmpeg -rtsp_transport tcp -i "rtsp://账号:密码@IP:端口/cam/realm ...

  6. 【Hadoop离线基础总结】oozie调度shell脚本

    目录 1.解压官方提供的调度案例 2.创建工作目录 3.拷贝任务模板到工作目录当中去 4.随意准备一个shell脚本 5.修改模板下的配置文件 6.上传调度任务到hdfs上面去 7.执行调度任务 1. ...

  7. electron——通知

    所有三个操作系统都提供了应用程序向用户发送通知的手段. Electron允许开发者使用 HTML5 Notification API 发送通知,并使用当前运行的操作系统的本地通知 API 来显示它. ...

  8. (Python基础教程之八)Python中的list操作

    Python基础教程 在SublimeEditor中配置Python环境 Python代码中添加注释 Python中的变量的使用 Python中的数据类型 Python中的关键字 Python字符串操 ...

  9. 开源电影项目源码案例重磅分析,一套代码发布小程序、APP平台多个平台

    uni-app-Video GitHub地址:https://github.com/Tzlibai/uni-app-video 一个优秀的uni-app案例,旨在帮助大家更快的上手uni-app,共同 ...

  10. Python dict字典方法完全攻略(全)

    我们知道,Python 字典的数据类型为 dict,我们可使用 dir(dict) 来查看该类型包含哪些方法,例如: >>> dir(dict)['clear', 'copy', ' ...