delphi解析TFDJsonDataSets生成的TJsonObject对象时提示存在于多字节代码页中的Unicode字符没有映射

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直接还原为字符串

喜欢的话,就在下面点个赞、收藏就好了,方便看下次的分享:

猜你喜欢

转载自blog.csdn.net/pulledup/article/details/104310217