解决对象序列化与反序列化失败且第一个字符为”问号”难题
最近遇到对象在序列化与反序列化过程中出现异常。下面就来详细说说这个“对象转成json后转成byte[]后再转成string,反序列化时提示失败且第一个字符是问号”的问题,以及该如何解决。
一、问题复现
在实际开发场景中,我们需要将一个对象先转成json格式,再把json转成byte[] ,通过网络传输后,再将其反序列化为对象。然而,在反序列化这一步却出现了错误。当打印出要反序列化的json字符串时,发现其开头多了一个问号,这就是导致反序列化失败的“罪魁祸首”。
为了让大家更清楚,下面看看具体的代码示例。
(一)相关类定义
- TextMessage类:
using System.Threading; namespace SocketTools { public class TextMessage : Message { private string message; public string Message { get => message; set => message = value; } public TextMessage() { } public TextMessage(string userName, string targetName, string sendTime, string message) : base(userName, targetName, sendTime, MessageType.Text) { this.message = message; } } }
这个类继承自Message
类,用于表示文本消息,包含消息内容等属性。
- Message类:
using Newtonsoft.Json; namespace SocketTools { public enum MessageType { Connection = 0, Text, Image, Mixed, Recall, File, } public class Message { private string userName; private string targetName; private string sendTime; private MessageType messageType; public string UserName { get => userName; set => userName = value; } public string TargetName { get => targetName; set => targetName = value; } public string SendTime { get => sendTime; set => sendTime = value; } public MessageType MessageType { get => messageType; set => messageType = value; } public Message() { } public Message(string userName, string targetName, string sendTime, MessageType messageType) { UserName = userName; TargetName = targetName; SendTime = sendTime; MessageType = messageType; } // 序列化方法 public static string Serialize(Message message) { return JsonConvert.SerializeObject(message); } // 反序列化方法 public static T Deserialize<T>(string json) where T : Message, new() { return JsonConvert.DeserializeObject<T>(json ); } } }
Message
类是一个基础类,定义了消息的一些通用属性和序列化、反序列化方法,其中MessageType
是一个枚举类型,用于表示消息的类型。
(二)执行代码
using SocketTools; using System; using System.IO; using System.Text; using System.Text.RegularExpressions; namespace FunctionalTesting { internal class Program { private static void Main(string[] args) { string msg = "消息内容Q1_2_end"; string userName = "AAAAAAAAAAAAAAAA"; string targetUserName = "CCCCCCCCCCCCCCCC"; TextMessage textMessage = new TextMessage(userName, targetUserName, DateTime.Now.ToString("yy-MM-dd-H:m:s"), msg); // 按照指定的格式创建消息字符串 string message = Message.Serialize(textMessage); Console.WriteLine(message); // 将消息字符串转换为字节流 byte[] data; using (MemoryStream ms = new MemoryStream()) { using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8)) { sw.Write(message); sw.Flush(); ms.Position = 0; data = ms.ToArray(); } } string data2 = Encoding.UTF8.GetString(data); Console.WriteLine(data2); data2 = Regex.Replace(data2, @"^s+", ""); Console.WriteLine(data2); var d = Message.Deserialize<TextMessage>(data2); Console.WriteLine(d.TargetName); Console.WriteLine(d.UserName); Console.WriteLine(d.MessageType); Console.ReadLine(); } } }
在这段代码中,我们创建了一个TextMessage
对象,将其序列化后转换为字节流,再转换回字符串,最后尝试反序列化。
(三)执行效果与错误日志
执行上述代码后,输出的json字符串开头出现了问号:
?{"Message":"消息内容Q1_2_end","UserName":"AAAAAAAAAAAAAAAA","TargetName":"CCCCCCCCCCCCCCCC","SendTime":"23-12-04-1:14:43","MessageType":1}
同时,反序列化时抛出错误,错误日志如下:
Newtonsoft.Json.JsonReaderException HResult=0x80131500 Message=Unexpected character encountered while parsing value: . Path '', line 0, position 0.
但打印原本未经过转换的对象序列化后的字符串,是没有开头这个问号的。
二、解决方式
针对这个问题,有两种常见的解决办法。
(一)去除前导字符
修改反序列化代码,直接去除字符串开头的前导字符。可以使用TrimStart
方法,代码如下:
// 反序列化方法 public static T Deserialize<T>(string json) where T : Message, new() { return JsonConvert.DeserializeObject<T>(json.TrimStart('uFEFF')); }
这里的uFEFF
就是导致问题的特殊字符,通过TrimStart
方法将其去除后,反序列化就能正常进行。
(二)条件判断后去除
还可以通过判断字符串的第一个字符来决定是否去除。如果第一个字符不是{
或者[
,就去除第一个字符,代码如下:
public static T Deserialize<T>(string json) where T : Message, new() { if (json[0] == '{' || json[0] == '[') { } else { json = json.Remove(0, 1); } return JsonConvert.DeserializeObject<T>(json); }
这种方式更加灵活,能适应更多复杂的情况。
三、问题原因
出现这个问题的根源在于零宽度不中断空格(Unicode U+FEFF)字符,也就是常说的BOM(Byte Order Mark)。在使用Unicode编码的文本文件或数据流中,BOM字符用来表示字节顺序。比如在UTF – 16编码里,一个字符由两个字节组成,不同系统和平台下这两个字节的顺序可能不同,BOM字符就能帮助解析器确定字节顺序,正确解码文件中的其他字符。
但在网络通信、数据库存储等数据交换场景中,BOM字符可能会被意外包含在字符串里,这就会导致解析错误。所以在处理这类字符串前,检查并移除不需要的BOM字符是很有必要的。
通过以上对问题的复现、解决方式的介绍以及原因剖析,希望大家在遇到类似问题时,能够快速定位并解决。