Code Monkey home page Code Monkey logo

magic-byte's Introduction

魔法字节(magic-byte)

GitHub (pre-)release GitHub issues GitHub closed issues GitHub

中文|English

1. 简介

在当代物联网行业中,由于隐私和安全问题,很多的公司选择使用自定义的私有二进制协议。 在C语言中,由于有结构体的加持,对象和字节数组转换起来就特别简单;但在java中,在没有原生支持的情况下,开发人员就只能够靠码力去读取解析数据然后转译成为对象 ,流程如下图:

在这看似简单的编码/解码过程中其实会伴随很多人头疼的问题,例如:

  • 大小端/网络字节序的处理
  • 无符号数/有符号数的处理
  • 多字节整数转换处理
  • ASCII码与字节之间的转换处理
  • 空指针/填充数据的处理
  • 数组对象/嵌套对象的处理

此项目的目的便是尽可能的解决上述问题,让大家将更多的时间聚焦在业务中。 在引入MagicByte后,只需要在类定义的同时使用注解声明字段的数据类型。 接下来就只需要调用两个简单的方法即可进行序列化:用于对象转字节的MagicByte.unpack();和用于字节转对象的MagicByte.pack()。 是不是很简单?马上试试吧!

2. 快速入门:

  1. 引入Jar包;
  2. @MagicClass对当前类进行全局配置
  3. @MagicField对需要转换的JAVA对象属性进行标注,支持对象组合嵌套,注意:不支持继承
  4. 使用MagicByte.pack()MagicByte.unpack()对数据或对象进行快速的序列化或反序列化
  5. (可选)支持使用MagicByte.registerCMD注册消息到 MagicByte, 前往查看示例
  6. (可选)支持使用@MagicConverter()注解来实现自定义序列化;前往查看枚举类自定义序列化示例

Maven项目可直接导入: 点击查看版本列表

<dependency>
  <groupId>io.github.misterchangray</groupId>
  <artifactId>magic-byte</artifactId>
  <version>2.4.2</version>
</dependency>

3. 代码示例

以下为简单的框架功能展示,实际项目中数据实体类定义建议参考 数据实体定义的最佳实践

下面的报文示例中, 共有 Student 和 School 两个数据报文:

// declare class must use public
// 使用大端模式, 默认为大端
@MagicClass(byteOrder = ByteOrder.BIG_ENDIAN)
public class School {
    // 10 byte, 普通数据类型, 占用10字节长度
    @MagicField(order = 1, size = 10)
    private String name;
    // 2 byte, 长度字段, 数据序列化时将自动填充实际数据长度
    @MagicField(order = 3, calcLength = true)
    private short length;
    // 支持组合模式, 这里嵌入了 Student 对象
    // 总字节数 = students.bytes * length
    @MagicField(order = 5, size = 2)
    private Student[] students;
    // 0 byte, 注意, 此处无法序列化, 不支持的数据类型将会被忽略
    @MagicField(order = 7)
    private List<Object> notSupport;
    // 0 byte, 注意, 此处无法序列化, 不支持的数据类型将会被忽略
    @MagicField(order = 9)
    private Object age;
    // 4 byte, 注意, 此处指定为秒级时间戳, 同时指定使用4个字节保存, 未指定则默认6个字节
    @MagicField(order = 13, size = 4, timestampFormat = TimestampFormatter.TO_TIMESTAMP_SECONDS)
    private Date[] birthdays;
    // 1 byte, 普通数据类型, 通过order配置序列化顺序, 序列号顺序和定义顺序无关
    @MagicField(order = 15)
    private byte age;
    // 1 byte, 校验和字段, 序列化时如提供计算函数则将会自动填充
    @MagicField(order = 17)
    private byte checkCode;

   
    // getter and setter ...
}

@MagicClass()
public class Student {
    // 10 byte, 普通数据, 长度为 10 字节
    @MagicField(order = 1, size = 10)
    private String name;
    // 4 byte, 普通数据, 整数, 此字段决定后续 phones 字段长度
    @MagicField(order = 5)
    private int length;
    // 总字节数 = phones.size * length
    // 单个元素 8 byte, 此List并未直接指定大小, 大小由 length 字段决定. length字段数据类型只能为 byte, short, int, UNumber
    @MagicField(order = 10, dynamicSizeOf = "length")
    private List<Long> phones;
    // 1 byte
    @MagicField(order = 15)
    private byte age;
    // 生日, 这里为秒级时间戳, 指定使用4个字节, 日期类型未指定则默认6个字节
    @MagicField(order = 18, size = 4, timestampFormat = TimestampFormatter.TO_TIMESTAMP_SECONDS)
    private Date birthDay;
    // getter and setter ...
}

public class Hello {
    void main() {
        // 全局配置校验和计算函数
        MagicByte.configMagicChecker(Checker::customChecker);
        School school = new School();
        school.setAge((byte) 23);
    
        // you can set other propertis
        // object to bytes
        // 也可以单独传入计算函数
        byte[] bytes = MagicByte.unpack(school, Checker::customChecker); 
        School school2 = MagicByte.pack(bytes, School.class); // bytes to object
        System.out.println(school.getAge() == school2.getAge()); // out put true
    
    }
}

public class Checker {
    /**
     * 序列化时: data数据中包含所有已序列化的数据(包括 calcLength 也已经调用并序列化)
     * 反序列化时: data数据为传入数据的副本
     * @param data
     * @return
     */
    public static byte[] customChecker(byte[] data) {
        return new byte[]{0xff};
    }
}

4. 注解和属性说明

工具存在三个注解:

  1. @MagicClass() 类注解; 主要用于数据全局配置
    • byteOrder 配置序列化大小端,可全局配置
    • strict 严格模式, 默认false, 严格模式将会抛出更多的异常
  2. @MagicField() 属性注解, 未注解的属性不参与序列化/反序列化过程
    • order 定义对象属性的序列化顺序(重要, 投入使用后请勿修改, 从1开始递增,建议跳跃配置如:1,3,5...)
    • size 属性大小, 仅UNumber&String&List需要设置, String/UNumber 代表字节长度. List和Array代表成员个数
    • cmdField 标记此字段为消息类型, 此配置结合消息注册使用. 默认false; 消息注册参考
    • charset 字符集设置,可全局配置, 仅String设置有效; 默认ASCII
    • dynamicSize 标记字段为动态长度, 整条消息只能标记一次且仅能标记String&List&Array类型字段; 点击查看详情
    • dynamicSizeOf 从指定的属性中获取List或Array的长度, 仅List,Array,String有效;引用字段类型只能为byte, short, int, UNumber
    • calcLength 标记字段为长度字段, 反序列化时将自动将数据总长度(字节数)填充到此字段; 可能抛出: InvalidLengthException
    • calcCheckCode 标记字段为校验和字段, 序列化或反序列化时将会校验或自动填充; 可能抛出: InvalidCheckCodeException
    • timestampFormat 可指定时间格式,时间戳或者文本,时间戳可指定为毫秒,秒,分钟,小时,天;日期类型默认6字节储存空间,可使用size进行调整;如秒级时间戳4个字节就足够储存传输
    • formatPattern 日期字段使用,如指定序列化为字符串,这里配置序列化格式。默认为:yyyyMMddHHmmss
  3. @MagicConverter()配置自定义序列化,更多说明参考 自定义序列化最佳实践
    • converter, 序列化类, 该类必须为MConverter的子类
    • attachParams, 附加参数;序列化时将会传入
    • fixSize, 固定数据字节长度, 可以统一指定自定义数据的长度,也可忽略然后在序列化时返回实际数据长度

5. 支持的数据类型及字节大小;

数据类型 数据类型 扩展类型(无符号数) 字节大小
byte boolean UByte 1
short char UShort 2
int float UInt 4
long double ULong 8
Date,Instant,DateTime LocalTime,LocalDate,LocalDateTime 6(配合size修改)
String UNumber custom(配合size指定)

6. 注意事项

  1. 因为order属性为对象序列化顺序,所以已投入使用的字段请不要随意修改order属性(重要),这样可能会影响已有业务;新增字段请递增使用新的order
  2. 大端小端使用@MagicClass进行配置
  3. 基本数据类型使用下表默认字节长度, String/List/Array/UNumber 需要使用size属性指定成员长度或字符串字节长度
  4. 请使用基础类型定义报文结构,目前仅支持以下数据类型:
    1. 四类八种基础类型(byte/char/short/int/long/float/double/boolean)
    2. 无符号包装类型(UByte/UShort/Uint/ULong/UNumber) 关于无符号数类型
    3. 支持 String, 但必须申明 Size
    4. 支持 List & Array, 可以使用泛型; 仅支持一维数组且不能使用可变数据类型,如:List<String>String[]List<UNumber>
  5. 数据溢出时工具会自动对数据进行裁剪,如字符串或数组长度声明为5, 则只会序列化集合前5个元素
  6. 字符串默认使用ASCII编码,可局部配置或全局修改
  7. 不支持一维以上的List或者Array
  8. boolean值 0=false/非0=true
  9. 所有类的定义必须为 public, 不支持私有内部类; 支持 public static class XXX {}
  10. 不支持类继承的序列化和反序列化;支持类的嵌套组合使用
  11. 序列化null值,如果是包装数据类型,则使用原始类型默认值;如Short a = null; 序列化为 0; 其他数据类型将会直接填充,如数组,对象等。
  12. 更多问题请前往WIKI页面查看 >> WIKI_HOME
  13. 内置枚举类序列化转换器SimpleEnumConverter,原理是根据枚举定义顺序进行序列化/反序列化.故使用后请不要更改定义顺序。建议自行实现

7. 开发建议

  1. 不建议网络传输浮点数;
  2. 不建议网络传输有符号数即负数
  3. 不建议网络传输字符串
  4. 不建议类中大量声明字符串
  5. 条件允许建议使用protobuf作为序列化框架
  6. 二进制协议建议单包大小在100KB以内
  7. order属性建议跳跃配置(如1,3,5,7), 且对于已经投产使用的字段一定不要随意修改顺序; 新增字段时必须使用递增后的order新值; 重要的事强调3次

8. 最后

本人目前也是做物联网相关领域的(共享充电宝), 开始的时候也是走了不少弯路,这个项目算是在这工作了1年多的工作总结; 写完之后才发现谷歌早在13年前就开源了类似框架, 真是学习使人进步; 又重复造轮子了。 附上谷歌的链接:

9.欢迎大佬入驻交流:

QQ群  562371124

magic-byte's People

Contributors

fulabula avatar misterchangray avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

magic-byte's Issues

如何支持二维的数组呢 ,就是吧这样的二维的数据 怎么放入

 如何支持二维的数组呢  ,就是吧这样的二维的数据 怎么放入

@magicfield(order = 15, size = 4)
private List<List> param = List.of(
List.of(new UserInfo(), new UserInfo()),
List.of(new UserInfo(), new UserInfo()),
List.of(new UserInfo(), new UserInfo()),
List.of(new UserInfo(), new UserInfo())
);

@MagicClass(byteOrder = ByteOrder.LITTLE_ENDIAN)
@DaTa
public static class UserInfo {

    @MagicField(order = 7)
    private short name;

    @MagicField(order = 9)
    private byte age;

}

 如何支持二维的数组呢  ,就是吧这样的二维的数据 怎么放入

 [
  [{"name":"t","age":1},{"name":"t","age":1}],
  [{"name":"t","age":1},{"name":"t","age":1}],
  [{"name":"t","age":1},{"name":"t","age":1}]

]

能否对整个 list 设置自定义序列化

目前的自定义序列化是对 list 遍历,然后对 list 里每一个 item 单独做序列化处理的,但是我希望可以对整个 list 做序列化,也就是直接把 list 给我,我来对 list 做整体的处理

dynamicSizof wiki 没更新

dynamicSizof wiki 没更新,新的 dynamicSizof 的改动是破坏性的,旧代码都要进行修改 >_<

希望增加时间类的序列化支持

增加对时间类:Date、Instant、LocalDate、LocalTime、LocalDateTime等的支持,默认按照 yyyy-MM-dd HH:mm:ss 格式及长度转换数据,或者根据指定的时间格式转换对应的值,也可以自己设定长度。同时除了这种方式还可以指定按照 int 或者 long 直接将时间转为秒或者毫秒的方式转换

异常栈无法看出报错位置

目前的异常栈在报错的时候只会打印一个报错信息,如果是配置序列化没配对的情况则无法看到具体序列化或者反序列化到哪个类的哪个属性的时候出现的问题

dynamicSize 支持接口类

如果 dynamicSize 可以支持 Object,是不是就可以运行时自动计算类型为接口类的属性的长度了

ClassParser

Hello Ray,

I have some doubt about the line 94 of the ClassParser
dynamicRef.setDynamicRef(fieldMetaInfo);
why do you set the dynamicRef to the fieldMetaInfo ?

使用日志门面代替控制台打印

e.printStackTrace() 只能输出到控制台,没法通过日志门面向各个日志框架写到日志文件中。我感觉 lombok 可以不引入,slf4j 的的引入并不会造成太大的影响,而且 slf4j 属于日志门面,适配各个日志框架,灵活性要更高一些,引入之后也不会对项目造成影响。而且,一般做项目大多数都是搭配日志框架输出的,很少直接用控制台输出的,这样定位问题也不方便。

dynamicSize & dynamicSizeOf 不能在一个类中同时存在吗?

有两点疑问:
1)dynamicSize 是标记动态计算的(总数-其他标记了长度=剩下的,就是它的数据), dynamicSizeOf 是标记从哪个成员上取长度的。它俩应该不冲突的。 我注释了检测,运行后的结果符合我的预期,但是不确定是否存在其他问题。
2)dynamicSize 必须带有 size 属性,然而size会自动裁剪。但是这个动态计算的长度是不固定的,我也不能确定size预设多少,只能将size标记足够大吗?对内存使用有影响吗?

提个建议,MagicField 属性的converter参数可以接受你的ConverterUtil方法

应用场景 就是 我的其中一个字段需要用 ushort去接。但是java中又没有ushort,ConverterUtil.byteToNumber(byte[]),你有提供了类似的工具类。那么结合起来岂不是很爽。如果能像我说的 那样直接转换的话,会方便许多。
image
例如这么写。直接在属性那块就把heading的值矫正过来了。

如果 dynamicsizeof 指向的属性名重名了,会导致序列化结果错误

如果我的属性和内部类属性都有一个叫 length 的字段,在使用的时候就会导致序列化的时候只序列化了 length 字段,他标记的字段就不会被序列化
代码示例如下:

@MagicClass
class A {

@MagicField(order = 1)
private int length;

@MagicField(order = 2, dynamicSizeOf = "length")
private String value;

@MagicField(order = 3)
private int dataLength;

@MagicField(order = 4, dynamicSizeOf = "dataLength")
private List<B> data;

@MagicClass
public static class B {

@MagicField(order = 1)
private int length;

@MagicField(order = 2, dynamicSizeOf = "length")
private byte[] data;
}

}

这个在 2.4.0 之前的实现是不会出问题的,换成 2.4.0 之后才出的问题。导致我序列化的时候 length 序列化完了就直接跳到 dataLength 了(但是 length 的值是对的),没有序列化 value 的内容,当我把两个 length 属性的名字改成不一样的之后问题就解决了

建议 dynamicSizeOf 支持表达式

原先只考虑过长度是另一个属性的情况,所以直接指定对应的 order 即可拿到长度,但是如果长度被封在了一个 bean 里,那么直接指定 order 是那个封装的对象,长度在封装的对象里里面,也就没法取了

支持“yyyy-MM-dd HH:mm:ss” 等格式的时间转换

由于协议比较老,部分地方使用的还是 yyyy-MM-dd HH:mm:ss 格式的字符串传的时间信息,而不是使用的秒数,所以希望可以以字符串的形式支持这种格式的时间和二进制之间的转换。也可以将这种转换提供为默认的实现了 MConverter 接口的自定义转换类的方式提供支持

探讨消息注册

  1. 在合并请求 #32 提交的测试用例可以正常序列化数据,但是反序列化后的对象为 null。
  2. wiki 里 https://github.com/MisterChangRay/magic-byte/wiki/%E6%95%B0%E6%8D%AE%E5%AE%9E%E4%BD%93%E5%AE%9A%E4%B9%89%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5#2-%E8%87%AA%E8%A1%8C%E8%A7%A3%E6%9E%90%E6%95%B0%E6%8D%AE%E5%92%8C%E4%BB%A3%E7%A0%81 的测试用例是不是有问题,先 Head pack = MagicByte.pack(deviceUploadMessage, Head.class); ,然后 HeartbeatCmd pack1 = MagicByte.pack(deviceUploadMessage, HeartbeatCmd.class); 用同一个二进制数据从头开始解析,数据结果是不对的,因为需要偏移起始位置,但是又要先获得 Head 的全长再去偏移生成对象,比较繁琐

BUG:子类继承父类的时候序列化结果为空数组

@MagicClass
public class ParentClassA {

    @MagicField(order = 1)
    private int number;

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}

@MagicClass
public class ChildClassA extends ParentClassA {

}

    /**
     * 测试constom converter 配置 dynamicsize 使用
     *
     * @throws ParseException
     */
    @Test
    public void testCustomConverterOfDynamicSize() throws ParseException {
        ChildClassA classA = new ChildClassA();
        classA.setNumber(1);

        byte[] data = MagicByte.unpackToByte(classA);
        Assert.assertEquals(4, data.length);

    }

java.lang.AssertionError:
预期:4
实际:0
<点击以查看差异>

对不同版本协议的支持

  1. 可能项目需要兼容多个版本的协议,不同版本间的字段不相同,但是目前的状态一个类无法根据版本选择性的对类中的一部分属性进行序列化,希望可以增加
  2. 除此之外某些版本不支持的属性也希望可以配置默认值在反序列化之后自动就有值了

无法计算接口类的长度

如果 @magicfield 注解的属性类型是接口类则计算的长度为 0,因为在计算长度的时候是通过类的属性计算的,而不是通过实际类型动态计算的

dynamicSize & dynamicSizeOf only use one in the class

class A {
/**
* size 必须设置,为了保险起见,默认给了100个数量,应该够了...
* 没有长度标识,只能动态计算,剩下的就是它
/
@magicfield(order = 4, size = 100, dynamicSize = true) //动态长度,从数据中获取...
protected List《B》 list;
}
class B{
/
*
* 值长度,将值转化为byte[]后的值长度...即:value.size()
*/
@magicfield(order = 2)
private int len;

/**
 * 真实数据
 * 需要自行将数据格式转为byte..
 */
@MagicField(order = 3, dynamicSizeOf = 2)
private ArrayList<Byte> value;

}
这种情况下,只能将B改为Byte,然后每次都得分为多次进行编解码吗?或者写很多自定义序列化器?
因为 Class B 有很多种定义 。

BUG:作为属性的类的属性如果都使用了 @MagicConverter 将会导致反序列结果不正确

测试代码参考合并请求 #56

@MagicClass
public class CustomClassA {

    @MagicField(order = 1)
    private int number;

    @MagicField(order = 2)
    private B b;

    @MagicClass
    public static class B {

        @MagicField(order = 1)
        @MagicConverter(converter = TypeEnumConverter.class)
        private TypeEnum typeEnum;
    }

    public enum TypeEnum {
        A, B
    }

目前通过调试追踪代码看到,在这种使用情况下,生成的属性 b 的 elementbytes 的值为 0,导致反序列化的时候跳过了对应属性的反序列化,导致反序列化结果里 b 的值为 null

可以设置默认全局端序

一般端序都是一个项目都用同一个,没必要在 @MagicClass(byteOrder = ByteOrder.BIG_ENDIAN) 中挨个设置,可以全局统一设置默认端序,如果有单独需要可以在注解里改

如果是动态的有办法呢?

如:
当第0个字节是 0x01的时候,则第1 ~ 4个字节 需要解析为:对象中的姓名
当第0个字节是0x02的时候,则第1 ~ 3个字节需要解析为:对象中的地址

针对 dynamicSize & dynamicSizeOf 不能在一个类中同时存在,做的修订,不确定是否存在隐藏问题

在 CollectionReader.java 文件,readFormBuffer 方法中,

if(TypeEnum.LIST == this.fieldMetaInfo.getType()) {
long allocSize = count * fieldMetaInfo.getGenericsField().getElementBytes();
ExceptionUtil.throwIFOOM(allocSize, fieldMetaInfo.getGenericsField().getFullName());

        //FIXME : 取消了count
        //List<Object> list = new ArrayList<>(count);
        List<Object> list = new ArrayList<>();

        for(int i=0; i<count; i++) {

            //FIXME 添加了if判断,尝试解决如下问题:
            //FIXME 针对List<T> dySize = true,  并且T中含有dySizeOf的问题
            //FIXME 针对 类下同时存在  ArrayList<Byte> dySizeOf 和  List<T> dySize = true, order=最后  的问题
            //TODO 需要构建大量的测试类...
            //TODO 需要测试 Size 过大 或者 过小问题
            //目前简单测试无问题
            //<1> 注释掉dySize 和 dySizeOf同时存在的检测
            //<2>
            //T中包含dySizeOf且没有计算,导致T.length长度偏小
            //suffixBytes 好像永远是0 或者小于0?,故导致fillBytes偏大或者正常
            //故fillBytes/T.length 计算出来的count偏大,但是buffer的读取有效数据是不变的
            //故根据buffer是否存在有效数据,跳出循环; 否则在List中会返回很多空T
            if(buffer.capacity() - buffer.position() <= 0) break;

            list.add( fieldMetaInfo.getGenericsField().getReader().readFormBuffer(buffer, entity));
        }

        return list;
    }

经过简单测试,序列化和反序列化 没有问题。因为没有全部翻阅内部处理流程, 就得作者从代码流程处理的角度考虑了。

动态嵌套对象的序列化和反序列化

根据我需要,我分数据头和数据体两部分,头的格式是固定的,数据体根据也不同结构也不一样,我能否直接通过仅在一个对象里封装头和数据体,来进行序列化和反序列化,就像下面代码的样子。或者别的方法来实现动态选择不同的数据体格式做序列化和反序列化?我不想先从二进制解出来一个头,然后拿着剩余的二进制数据再判断解析数据体,太麻烦了。

注:我知道 readme 写了不支持 Object,所以我下面的代码里给 Object 加个自定义转换器的注解,能否实现动态序列化转换?

@MagicClass(byteOrder = ByteOrder.BIG_ENDIAN)
public class School {
    // 一个对象
    @MagicField(order = 1)
    private Header header;
    
    @MagicField(order = 3)
    @MagicConverter()
    private Object age;
  
}

自定义序列化器 对于 属性List<> 不会触发

@MagicClass
public class Staff4 {
@magicfield(order = 1)
private int id;
@magicfield(order = 2, calcLength = true)
private int length;
@MagicConverter(converter = CustomBook3Converter.class, attachParams = "1")
@magicfield(order = 5, size = 2)
private List book; // 序列化器 不会触发
@MagicConverter(converter = CustomBook3Converter.class, attachParams = "2")
@magicfield(order = 7, size = 2)
private List book2; // 如果定义改为 private Book3 book2; 则序列化器会触发。
@magicfield(order = 14, size = 4)
private String name;

序列化时 dynamicSizeOf 对应属性值与数组、列表长度不一致时能通过检查无 warn 警告或抛出异常

    @MagicField(order = 1)
    private int teacherLength;

    @MagicField(order = 2, dynamicSizeOf = 1)
    private Teacher[] teacher;

    @MagicField(order = 3)
    private int studentLength;

    @MagicField(order = 4, dynamicSizeOf = 3)
    private List<Student> studentList;

    public static List<Classes> build(int count) {
        List<Classes> classes = new ArrayList<>();
        for (int i = 0; i <count; i++) {
            Classes classes1 = new Classes();
            classes1.setTeacher(Teacher.build(2).toArray(new Teacher[1]));
            classes1.setStudentList(Student.build(2));
            classes1.setStudentLength(0);
            classes1.setTeacherLength(0);
            classes.add(classes1);
        }
        return classes;
    }

teacherLength studentLength 强制赋值为 0,序列化结果
image
符合预期,但是序列化的过程中没有提示这里有不一致的情况或者抛出异常,不便于在实际使用时定位问题

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.