在前后端的数据协议(主要指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. 模块(类)之间解耦利器:EventPublishSubscribeUtils 事件发布订阅工具类

    如果熟悉C#语言的小伙伴们一般都会知道委托.事件的好处,只需在某个类中提前定义好公开的委托或事件(委托的特殊表现形式)变量,然后在其它类中就可以很随意的订阅该委托或事件,当委托或事件被触发执行时,会自 ...

  2. spring的aop的注解配置

    一.使用注解的方式配置后置通知 第一步,创建通知类LogAdvice 第二步,要在spring主配置文件中声明以注解的方式配置spring aop  第三步,测试 二.其他异常配置 package c ...

  3. 【Linux】1 创建目录:mkdir

    mkdir命令用于创建目录,如同一路径下创建单个或多个目录.递归创建目录,但同路径下不能创建同名目录,且目录名区分大小写. [命令] mkdir [用途] 创建目录(单个目录/多个目录) [语法] m ...

  4. LeetCode #188场周赛题解

    A题链接 给你一个目标数组 target 和一个整数 n.每次迭代,需要从 list = {1,2,3..., n} 中依序读取一个数字. 请使用下述操作来构建目标数组 target : Push:从 ...

  5. 带你看看Java的锁(一)-ReentrantLock

    前言 AQS一共花了5篇文章,对里面实现的核心源码都做了注解 也和大家详细描述了下,后面的几篇文字我将和大家聊聊一下AQS的实际使用,主要会聊几张锁,第一篇我会和大家聊下ReentrantLock 重 ...

  6. Vue + Element-ui实现后台管理系统(4)---封装一个ECharts组件的一点思路

    封装一个ECharts组件的一点思路 有关后台管理系统之前写过三遍博客,看这篇之前最好先看下这三篇博客.另外这里只展示关键部分代码,项目代码放在github上: mall-manage-system ...

  7. ActiveMQ 持久订阅者,执行结果与初衷相违背,验证离线订阅者无效,问题解决

    导读 最新在接触ActiveMQ,里面有个持久订阅者模块,功能是怎么样也演示不出来效果.配置参数比较简单(配置没几个参数),消费者第一次运行时,需要指定ClientID(此时Broker已经记录离线订 ...

  8. 【Hadoop离线基础总结】Hue与Mysql集成

    Hue与Mysql集成 1.修改hue.ini配置文件 这里要去掉#,打开mysql注释,大概在1547行 [[[mysql]]] nice_name="My SQL DB" en ...

  9. OpenCV 经纬法将鱼眼图像展开

    文章目录 前言 理论部分 鱼眼展开流程 鱼眼标准坐标计算 标准坐标系与球坐标的转换 代码实现 测试效果如下图 总结 this demo on github 前言 鱼眼镜头相比传统的镜头,视角更广,采集 ...

  10. 出现Please make sure you have the correct access rights and the repository exists.问题解决

    问题: 有一段时间没有用码云了,当输入 git push -u origin master命令出现Please make sure you have the correct access rights ...