delphi解析TFDJsonDataSets生成的TJsonObject对象时提示
存在于多字节代码页中的Unicode字符没有映射
一、问题的提出:
服务器端需要返回Json对象: function getTablesStruct :TJSonObject;
LFDJsonDatasets := TFDJsonDatasets.Create;
LFDJSONInterceptor:=TFDJSONInterceptor.Create;
Result:=TJSonObject.Create;
// 生成LFDJsonDatasets :
ProduceAbigTFDJSONDataSets(LFDJsonDatasets,
LDatasTab1Key,LDatasTab1Sql,
LDatasTab2Key,LDatasTab2Sql,
LDatasTab3Key,LDatasTab3Sql );
// 返回TJSonObject对象 :
ifFinished:=LFDJSONInterceptor.DataSetsToJSONObject(
LFDJsonDatasets,
Result );
现在如果:客户器端不用TDSRestConnection的delphi客户端连接来反序列化ATFDJSONDataSets:
TFDJSONInterceptor.JSONObjectToDataSets( ATJSONObject, ATFDJSONDataSets );
FDAdaptedDataSet := TFDJSONDataSetsReader.GetListValueByName(
ATFDJSONDataSets ,'取物品资料表');
而采用Rest办法:自然就需要解析服务器端返回的TJSonObject,而该Json对象中,包含类似这样的“乱码”:
Json解析出对值“乱码”:直接Base64解码后,UTF8编码还原时,会提示:
'No mapping for the Unicode character exists in the target multi-byte code page'.
即:存在于多字节代码页中的Unicode字符没有映射。
二、分析
1、这样的“乱码”是什么码,怎么来的
这样的“乱码”是“压缩流的二进制编码”的字符串,而压缩流的字符串是Base64对二进制格式适配数据集的内存流的编码的字符串,编码过程的源代码的流程如下:
--->从TFDJSONDataSets中获取需要返回客户端的TJSONObject
class function TFDJSONInterceptor.DataSetsToJSONObject(
const ADataSets: TFDJSONDataSetsBase;
const AJSONObject: TJSONObject): Boolean;
Result := ItemListToJSONObject(ADataSets.FDataSets, AJSONObject);
--->TFDJSONDataSets从其TItemList列表获取各项适配数据集的值
class function TFDJSONInterceptor.ItemListToJSONObject(
const AList: TItemList; const AJSONObject: TJSONObject): Boolean;
var
LPair: TFDJSONDataSets.TItemPair;
LActive: Boolean;
LJSONDataSet: TJSONValue;
LDataSet: TFDAdaptedDataSet;
for LPair in TFDJSONDataSets.TItemList(AList) do
begin
Result := True;
LDataSet := LPair.Value;
LActive := LDataSet.Active;
if not LActive then
LDataSet.Active := True;
try
LJSONDataSet := DataSetToJSONValue(LDataSet);
// Use AddPair overload that will accept blank key
AJSONObject.AddPair(TJSONPair.Create(LPair.Key, LJSONDataSet))
finally
if not LActive then
LDataSet.Active := False;
end;
end;
--->TFDJSONDataSets的TItemList列表中的各项适配数据集获取TJSONValue字符串:
function DataSetToJSONValue(const ADataSet: TFDAdaptedDataSet): TJSONValue;
var
S: string;
begin
S := DataSetToString(ADataSet);
Result := TJSONString.Create(S);
end;
--->TFDJSONDataSets的TItemList列表中的各项适配数据集获取什么格式的字符串:
--->:经压缩后的内存流中的被Base64编码的二进制字符串:
function DataSetToString(const ADataSet: TFDAdaptedDataSet): string;
var
LBinary64: string;
LMemoryStream: TMemoryStream;
LStringStream: TStringStream;
LDstStream: TMemoryStream;
Zipper: TZCompressionStream;
begin
LDstStream := TMemoryStream.Create; //:TNetEncoding.Base64.Encode编入TStringStream的目标内存流
try
LMemoryStream := TMemoryStream.Create; //:适配数据集保存入的二进制内存流
try
ADataSet.SaveToStream(LMemoryStream, TFDStorageFormat.sfBinary); //:将适配数据集保存入上面的二进制内存流
LMemoryStream.Seek(0, TSeekOrigin.soBeginning);
Zipper := TZCompressionStream.Create(clDefault, LDstStream); //:产生1个将上面的Base64目标内存流进行压缩的压缩流
try
Zipper.CopyFrom(LMemoryStream, LMemoryStream.Size); //:将上面适配数据集的二进制内存流压缩入Base64目标内存流
finally
Zipper.Free;
end;
finally
LMemoryStream.Free;
end;
LDstStream.Seek(0, TSeekOrigin.soBeginning);
LStringStream := TStringStream.Create; //:产生1个字符串流
try
TNetEncoding.Base64.Encode(LDstStream, LStringStream); //:将上面经压缩后目标内存流用Base64编码入字符串流
LBinary64 := LStringStream.DataString; //:返回列表各项适配数据集经压缩后的用Base64编码的字符串流中的字符串
finally
LStringStream.Free;
end;
finally
LDstStream.Free;
end;
Result := LBinary64;
end;
2、这样的“乱码”是什么?
这样的“乱码”是经压缩后的内存流中的被Base64编码的二进制字符串。
这样的“乱码”被封装成Json对象的TPair对的TJsonValue值,每个TJsonValue值,对应了原始TFDJsonDataSets中封装的各个数据集对象,每个TPair对的TJsonString属性,对应这些数据集对象的对象名。
3、结论:为何会:显示“乱码”,提示“存在于多字节代码页中的Unicode字符没有映射”?
'No mapping for the Unicode character exists in the target multi-byte code page'.
即:存在于多字节代码页中的Unicode字符没有映射。
2.3.1、因为你base64解码后的数据中包含压缩字节
2.3.2、这些压缩字节或被解压,其中的数据是二进制的字节
三、解析“乱码”的思路
3.1、思路分析
如上分析,解析过程应当是上述编码过程的逆向流程。
3.2、解析流程
3.2.1、解析出Json对象中的各个数据集对象的TJsonValue值,得到该数据集对象的经压缩后的内存流中的被Base64编码的二进制字符串,即其对应的“乱码”。
LJsonObj := TJSONObject.ParseJSONValue(
TEncoding.UTF8.GetBytes(LResult), 0, True) as TJSONObject;
LDescribe:='开始Json原生解析BJson_Base64解码,CtL00001的数据集:';
LResult:=(LJsonObj.P['result'].A[0].P['CtL00001'] as TJsonValue).Value;
//:解析时要注意Json路径格式,这个很重要
//:看看什么结果:
//WebBrowserGet(WebBrowser1,LResult);
3.2.2、对TStringStream进行Base64解码。
LTStreamofBase64Decode:=TMemoryStream.Create;
try
TNetEncoding.Base64.Decode(LStringStream,LTStreamofBase64Decode);
LTStreamofBase64Decode.Seek(0,TSeekOrigin.soBeginning);
finally
end;
3.2.3、对Base64解码后的TZCompressionStream压缩流进行解压缩TZDecompressionStream成myTStream。
try
LDeZipper:=TZDecompressionStream.Create(LTStreamofBase64Decode);
finally
LDisticMemoryStream:=TMemoryStream.Create;
try
LDisticMemoryStream.CopyFrom(LDeZipper, LDeZipper.Size);
finally
LDeZipper.Free; //:注意:解压缩流,必须得到释放后,才能获取到解压后的内存流
end;
end;
3.2.4、还原数据:使用内存表(即将二进制的内存流还原成适配的数据集TFDAdaptedDataSet)或下面两种方案
LDisticMemoryStream.Seek(0, TSeekOrigin.soBeginning);
LMemTable := TFDMemTable.Create(nil);
try
LMemTable.LoadFromStream(LDisticMemoryStream,TFDStorageFormat.sfBinary);
LMemTable.SaveToFile(FAppASubPath+'解压后的二进制流被LMemTable输出的JSON字符串.json',TFDStorageFormat.sfJSON);
LStrings.LoadFromFile(FAppASubPath+'解压后的二进制流被LMemTable输出的JSON字符串.json',TEncoding.UTF8);
LResult:=LStrings.Text;
finally
LDisticMemoryStream.Free;
LMemTable.Free;
end;
LTStreamofBase64Decode.Free;
3.2.4.1、方案1:目标数据集从解压后的流中加载原始二进制格式的流:
myDataSet.LoadFromStream(myTStream,, TFDStorageFormat.sfBinary)
3.2.4.2、方案2(其它开发语言的参考方案):解压后的二进制格式的流(BSon)进行转化(JSon):
到此,开始1个分水岭,也就是delphi与其它语言的资源共享问题:
上述这个解码并解压后的内存流LDisticMemoryStream,其字节数组中,流对应的 TJsonPair的JsonValue可以解开,但JsonString对应键值,似乎都进行了加密,如下步骤3.2.4.2.1所示。
因此好的方案是:
在服务器端做这个事情,而不要把它放在客户端留给资源的调用方,在服务器方法中,将TFDJsonDatasets的TJsonObject封装,转变为用内存表对各个数据集CopyDatasets后转存.json兵对其加载的TJsonObject封装,先行用内存表对base64解码解压后的流处理的
uses Data.FireDACJSONReflect;
function DataSetToString(const ADataSet: TFDAdaptedDataSet): string;
LDataSetBinaryCompressionStreamBase64DataString
:=DataSetToString(myFDAdaptedDataSet);
//:加密、压缩、Base64Encode编码
function MemTableFromString(const AValue: string): TFDMemTable;
FMemTable:=
MemTableFromString(LDataSetBinaryCompressionStreamBase64DataString);
//:解密、解压、Base64Decode解码的二进制格式内存流,把它存为Json格式文件:
FMemTable.SaveToFile('myFDAdaptedDataSetJson.json',sfJSON);
LStrings:=TStringList.Create;
LStrings.LoadFromFile('myFDAdaptedDataSetJson.json',sfJSON);
//:内存流从二进制格式还原成Json格式:
Result := TJSONObject.ParseJSONValue(
TEncoding.UTF8.GetBytes(LStrings.Text), 0, True) as TJSONObject;
//:LJsonObj重新封装用内存表解码解压后的UTF8格式的TJSONObject
//:回调给客户端
3.2.4.2.1、从解压后的二进制流中获取字节数组TBytes
var LJsonStr :string ; LJsonObject : TJsonObject ;
LJsonObject := TJsonObject.Create ;
try
LDisticMemoryStream.Seek( 0, TSeekOrigin.soBeginning );
LDisticMemoryStream.ReadBuffer(ATBytes , 0 , myTStream.size);
//: TMemoryStream.ReadBuffer (var Buffer: TBytes; Offset, Count: NativeInt); //:将流中数据逐个字节读入字节数组ATBytes
////////////////////二进制字节数组Buffer转16进制
LJsonStr :=TConverters.Bson2Json( ATBytes , [ExtendedMode, Indented] );
//: Converters:搜索路径:D:\PulledupO2O\myPublic\RTL\RTLSamples\Json;
//: 或:C:\Users\Public\Documents\Embarcadero\Studio\20.0\Samples\Object Pascal\RTL\Json
LJsonObject := TJSONObject.ParseJSONValue(
TEncoding.UTF8.GetBytes(LJsonStr), 0, True) as TJSONObject;
finally
LJsonObject.Free;
end ;
////////////////////:或:二进制字节数组Buffer直接还原为字符串
喜欢的话,就在下面点个赞、收藏就好了,方便看下次的分享: