likes
comments
collection
share

Java和Rust之间的JSON序列化互转解决方案

作者站长头像
站长
· 阅读数 5

我是蚂蚁背大象(Apache EventMesh PMC&Committer),文章对你有帮助给项目rocketmq-rust star,关注我GitHub:mxsm,文章有不正确的地方请您斧正,创建ISSUE提交PR~谢谢! Emal:mxsm@apache.com

1.背景

最近在使用Rust实现Rocketmq的项目 rocketmq-rust,在实现的过程中就会遇到请求头以及相关的数据JSON序列化在两个不同语言中的序列化和反序列的情况。在这种情况下遇到下面的问题:Java中存在继承,例如

public class Test {

    public static void main(String[] args) {
        Student object = new Student();
        object.setName("mxsm");
        object.setAge("18");
        object.setSchool("杭州电子科技大学");
        System.out.println(JSON.toJSONString(object));
    }
    
    public static class Person {
        private String name;
        private String age;
		//ignore get set method
    }

    public static class Student extends Person {
        private String school;
		//ignore get set method
    }
}

那么在Java中序列化就会打印成这样:

{"age":"18","name":"mxsm","school":"杭州电子科技大学"}

问题就来了。在Rust中没有这样的继承关系该如何处理这样的代码情况。随着跨语言开发的流行,将数据在不同编程语言之间进行序列化和反序列化变得越来越重要。本文将探讨如何在Java和Rust之间实现JSON数据的序列化互转,并解决在Rust中处理Java继承序列化的挑战。

2. Java和Rust之间的JSON序列化互转

Java中有许多库可以用于JSON序列化,其中最流行的是Jackson和Gson以及Fastjson。我们将以Fastjson为例进行介绍(Rocketmq中使用的就是Fastjson进行序列)。Rust中使用serde作为例子,在 Rocketmq-rust 项目中JSON序列化使用的是 serde 。

2.1 无继承的简单数据结构

Java示例代码:

 public static class Person {
        private String name;
        private String age;
     	private String homeAddress
		//ignore get set method
 }

转换成对应的Rust代码

#[derive(Clone,Debug,Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Person{
    name: String,
    age: String,
    home_address: String
}

这里有个需要说明,在Java规范中字段的命名遵循的是驼峰,而rust使用的snake case,两者是有区别的。 这里就需要注意如果是以Java为主的项目那么Rust的序列化和反序列化都应该使用驼峰的方式来进行。也就是上面代码的增加的 #[serde(rename_all="camelCase")] 而如果是Rust项目为主,很大可能序列后的JSON字符串使用的就是snake case 那么Java项目就需要进行相对应的处理。

2.2 Java代码有继承的结构

代码示例:

public static class Person {
        private String name;
        private String age;
		//ignore get set method
    }

    public static class Student extends Person {
        private String school;
		//ignore get set method
    }

在Java代码中继承是很常见的一种操作,但是在Rust中struct的继承并不存在。那么这种情况下应该如何解决。

2.2.1 将所有的代码扁平化

代码扁平化这个是最简单的方式。以上面代码为例。如果扁平化后我们后我们处理后的Rust代码:

#[derive(Clone,Debug,Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Person{
    name: String,
    age: String,
}

#[derive(Clone,Debug,Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Person{
    name: String,
    age: String,
    school: String
}

所谓扁平化就是直接将继承代码的属性移到最底层的继承代码中。这样做就是简单,能够实现和Java相同的序列化的效果。

优点:

  • 简单,快速解决问题

缺点:

  • 需要实现的重复代码量大

2.2.2 抽象出来trait

将公共的方法操作方法抽象出来trait然后,使用trait继承的方式来实现。同样已上面的代码为例:

pub trait PersonTrait{
    fn get_name(&self)->String;
    fn get_age(&self)->String;
}

然后所有的struct进行实现。这个优缺点也很明显。

优点:

  • 简单,相比第一种方式多了一个抽象trait的步骤,其他的和第一种差不多
  • 可以使用Trait作为方法的参数或者struct的属性。提供了类似Java的多态(这个对于无需要进行序列化的比较好)

缺点:

  • 需要实现的重复代码量大

2.2.3 使用struct聚合配合serde的扁平化(重点)

使用struct聚合配合serde的扁平化来解决Java项目和Rust项目的JSON数据的序列化和反序列交互比较优的解。通用用上面的代码为例子。如果使用这种方式来处理,转换成Rust代码后的代码:

#[derive(Clone,Debug,Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Person{
    name: String,
    age: String,
}

#[derive(Clone,Debug,Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Person{
    #[serde(flatten)]  // 这个是扁平化关键
    person: Persion
    school: String
}

同样定义跟Java相同的代码,然后通过聚合的方式来模拟Rust中的继承关系。 将需要扁平化的代码进行扁平化使用 #[serde(flatten)] 来实现。

优点:

  • 能够和其他语言相同的JSON序列化方式(需要serde的扁平化支持)
  • 整体的序列化相对较灵活

缺点:

  • 需要Rust JSON序列化工具的支持。如果工具不支持没办法实现。只能手动序列化

说明:使用struct聚合配合serde的扁平化来解决JSON序列化的方式也是 Rocketmq-rust 项目中的解决方式。因为rocketmq-rust需要支持和Java版本进行互通。

3.总结

对于Java JSON序列化和Rust JSON序列化主要差异由以下两种原因引起:

  • 属性的命名规范,Java使用的是驼峰 而Rust使用的是snake case。这就导致在序列化的情况下两者之间的序列化会存在属性的命名差异。
  • Java有继承关系,继承的最底层的类进行序列化会将父类的属性进行扁平化序列化。而Rust中不存在所以导致Rust中的序列化的差异性,为了解决这种差异性就有了上面的三种方式。

就单纯解决JSON序列化使用 使用struct聚合配合serde的扁平化 是一种比较忧的解决方式。但是不是唯一的方式。