如何使用DBXJSON将包含转义/特殊字符的字符串转换为JSON格式,并从JSON格式转换回字符串?

15
我在将一个包含转义字符的字符串转换为 TJsonString 转换时遇到了问题。(我使用的是 Delphi XE 2,Update 4,Hotfix 1)。
注意:我熟悉 SuperObject,但我的要求是使用 DBXJSON 单元。
看起来 TJSONString 在通过 ToString() 方法返回 JSON 表示形式时未正确转义。 我做错了什么(如果有的话),如何正确地将带有特殊字符的字符串转换为其正确的 JSON 表示形式? 也许我漏掉了什么,但以下任何一个 Q&A 都似乎没有直接解决这个问题:

编辑:

事实证明,下面的示例确实按预期工作。

对我不清楚的是,当通过它的构造函数创建一个 TJSONString 并将其添加到 TJSONObject 中时,ToString() 方法将返回一个转义的表示形式。但是,在解析 TJSONObject 后,ToString() 方法将返回未转义的表示形式。

唯一的其他注意事项是,样本代码中的 EscapeString() 函数正在处理双引号。虽然我在这里没有使用双引号,但我的其他一些代码使用了双引号,这导致解析失败,因为 TJSONString 已经转义了该字符。我已更新示例代码,从 EscapeString() 函数中删除此处理,这是我在自己的类中使用的。

再次感谢 @Linas 的答案,帮助我“get”到了它。

原始字符串值:
Text := 'c:\path\name' +#13 + #10 + 'Next Line';

Text: c:\path\name
Next Line

DBXJSON 的输出结果(无需转义):

JsonString: "c:\path\name
Next Line"

JsonPair: "MyString":"c:\path\name
Next Line"

JsonObject: {"MyString":"c:\path\name
Next Line"}

解析未转义的文本失败:

Text to parse: {"MyString":"c:\path\name
Next Line"}

Parsed JsonObject = *NIL*

我希望DBXJSON能够产生以下结果:

Escaped String: c:\\path\\name\r\nNext Line

JsonString: "c:\\path\\name\r\nNext Line"

JsonPair: "MyString":"c:\\path\\name\r\nNext Line"

JsonObject: {"MyString":"c:\\path\\name\r\nNext Line"}

解析转义文本(无效)(使用JSONLint验证的待解析文本):

Text to parse: {"MyString":"c:\\path\\name\r\nNext Line"}

Parsed JsonObject.ToString(): {"MyString":"c:\path\name
Next Line"}

我注意到TJSONString只能正确处理双引号(")这个特殊字符。

这是我正在使用的代码:

program JsonTest;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, DbxJson;

function EscapeString(const AValue: string): string;
const
  ESCAPE = '\';
  // QUOTATION_MARK = '"';
  REVERSE_SOLIDUS = '\';
  SOLIDUS = '/';
  BACKSPACE = #8;
  FORM_FEED = #12;
  NEW_LINE = #10;
  CARRIAGE_RETURN = #13;
  HORIZONTAL_TAB = #9;
var
  AChar: Char;
begin
  Result := '';
  for AChar in AValue do
  begin
    case AChar of
      // !! Double quote (") is handled by TJSONString
      // QUOTATION_MARK: Result := Result + ESCAPE + QUOTATION_MARK;
      REVERSE_SOLIDUS: Result := Result + ESCAPE + REVERSE_SOLIDUS;
      SOLIDUS: Result := Result + ESCAPE + SOLIDUS;
      BACKSPACE: Result := Result + ESCAPE + 'b';
      FORM_FEED: Result := Result + ESCAPE + 'f';
      NEW_LINE: Result := Result + ESCAPE + 'n';
      CARRIAGE_RETURN: Result := Result + ESCAPE + 'r';
      HORIZONTAL_TAB: Result := Result + ESCAPE + 't';
      else
      begin
        if (Integer(AChar) < 32) or (Integer(AChar) > 126) then
          Result := Result + ESCAPE + 'u' + IntToHex(Integer(AChar), 4)
        else
          Result := Result + AChar;
      end;
    end;
  end;
end;

procedure Test;
var
  Text: string;
  JsonString: TJsonString;
  JsonPair: TJsonPair;
  JsonObject: TJsonObject;
begin
  try
    Writeln('Raw String Value');
    Writeln('-----------------');
    Text := 'c:\path\name' +#13 + #10 + 'Next Line';
    Writeln('Text: ', Text);
    JsonString := TJsonString.Create(Text);
    JsonPair := TJsonPair.Create('MyString', JsonString);
    JsonObject := TJsonObject.Create(JsonPair);
    // DBXJSON results
    Writeln;
    Writeln('What DBXJSON produces');
    Writeln('---------------------');
    Writeln('JsonString: ', JsonString.ToString);
    Writeln;
    Writeln('JsonPair: ', JsonPair.ToString);
    Writeln;
    Writeln('JsonObject: ', JsonObject.ToString);
    Writeln;

    // assign JSON representation
    Text := JsonObject.ToString;
    // free json object
    JsonObject.Free;
    // parse it
    JsonObject:= TJsonObject.ParseJsonValue(TEncoding.ASCII.GetBytes(
      Text), 0) as TJsonObject;
    Writeln('Parsing UN-escaped Text *FAILS* ');
    Writeln('----------------------------------');
    Writeln('Text to parse: ', Text);
    Writeln;
    if (JsonObject = nil) then
      Writeln('Parsed JsonObject = *NIL*')
    else
      Writeln('Parsed JsonObject: ', JsonObject.ToString);
    Writeln;
    // free json object
    JsonObject.Free;
    // expected results
    Text := 'c:\path\name' +#13 + #10 + 'Next Line';
    Text := EscapeString(Text);
    JsonString := TJsonString.Create(Text);
    JsonPair := TJsonPair.Create('MyString', JsonString);
    JsonObject := TJsonObject.Create(JsonPair);
    Writeln('What I *EXPECT* DBXJSON to produce');
    Writeln('----------------------------------');
    Writeln('Escaped String: ', Text);
    Writeln;
    Writeln('JsonString: ', JsonString.ToString);
    Writeln;
    Writeln('JsonPair: ', JsonPair.ToString);
    Writeln;
    Writeln('JsonObject: ', JsonObject.ToString);
    Writeln;
    // assign JSON representation
    Text := JsonObject.ToString;
    // free json object
    JsonObject.Free;
    // parse it
    JsonObject:= TJsonObject.ParseJsonValue(TEncoding.ASCII.GetBytes(
      Text), 0) as TJsonObject;
    Writeln('Parsing ESCAPED Text (*INVALID*) ');
    Writeln('----------------------------------');
    Writeln('Text to parse: ', Text);
    Writeln;
    Writeln('Parsed JsonObject.ToString(): ', JsonObject.ToString);
    Writeln;
    Readln;
  except
    on E: Exception do
    begin
      Writeln(E.ClassName, ': ', E.Message);
      Readln;
    end;
  end;
end;

begin
  Test;
end.
3个回答

10
您可以尝试定义自己的TJSONString类型并在其中转义json字符串。例如:
uses
  DBXJSON;

type
  TSvJsonString = class(TJSONString)
  private
    function EscapeValue(const AValue: string): string;
  public
    constructor Create(const AValue: string); overload;
  end;

{ TSvJsonString }

constructor TSvJsonString.Create(const AValue: string);
begin
  inherited Create(EscapeValue(AValue));
end;

function TSvJsonString.EscapeValue(const AValue: string): string;

  procedure AddChars(const AChars: string; var Dest: string; var AIndex: Integer); inline;
  begin
    System.Insert(AChars, Dest, AIndex);
    System.Delete(Dest, AIndex + 2, 1);
    Inc(AIndex, 2);
  end;

  procedure AddUnicodeChars(const AChars: string; var Dest: string; var AIndex: Integer); inline;
  begin
    System.Insert(AChars, Dest, AIndex);
    System.Delete(Dest, AIndex + 6, 1);
    Inc(AIndex, 6);
  end;

var
  i, ix: Integer;
  AChar: Char;
begin
  Result := AValue;
  ix := 1;
  for i := 1 to System.Length(AValue) do
  begin
    AChar :=  AValue[i];
    case AChar of
      '/', '\', '"':
      begin
        System.Insert('\', Result, ix);
        Inc(ix, 2);
      end;
      #8:  //backspace \b
      begin
        AddChars('\b', Result, ix);
      end;
      #9:
      begin
        AddChars('\t', Result, ix);
      end;
      #10:
      begin
        AddChars('\n', Result, ix);
      end;
      #12:
      begin
        AddChars('\f', Result, ix);
      end;
      #13:
      begin
        AddChars('\r', Result, ix);
      end;
      #0 .. #7, #11, #14 .. #31:
      begin
        AddUnicodeChars('\u' + IntToHex(Word(AChar), 4), Result, ix);
      end
      else
      begin
        if Word(AChar) > 127 then
        begin
          AddUnicodeChars('\u' + IntToHex(Word(AChar), 4), Result, ix);
        end
        else
        begin
          Inc(ix);
        end;
      end;
    end;
  end;
end;

使用示例:

procedure Test;
var
  LText, LEscapedText: string;
  LJsonString: TSvJsonString;
  LJsonPair: TJsonPair;
  LJsonObject: TJsonObject;
begin
  LText := 'c:\path\name' + #13 + #10 + 'Next Line';
  LJsonString := TSvJsonString.Create(LText);
  LJsonPair := TJsonPair.Create('MyString', LJsonString);
  LJsonObject := TJsonObject.Create(LJsonPair);
  try
    LEscapedText := LJsonObject.ToString;
    //LEscapedText is: c:\\path\\name\r\nNext Line
  finally
    LJsonObject.Free;
  end;
end;

以下是正确的解析方式:

//AText := '{"MyString":"c:\\path\\name\r\nNext Line"}';
function Parse(const AText: string): string;
var
  obj: TJSONValue;
begin
  obj := TJSONObject.ParseJSONValue(AText);
  try
    Result := obj.ToString;
    //Result := {"MyString":"c:\path\name
   //Next Line"}
  finally
    obj.Free;
  end;
end;

谢谢@Linas,但是根据我的示例代码,我能够自己完成转义。另一个问题是当包含转义字符串时,TJsonObject似乎不能正确解析JSON字符串。我希望问题依旧是出在我的身上 :) - Doug
@Doug 刚刚用 Delphi XE 进行了测试,一切都按预期工作。你可能以错误的方式解析了 JSON 字符串。请查看我的答案编辑,了解应该如何完成。 - Linas
感谢@Linas。在更详细地检查了我的代码后,我发现我的转义例程正在处理双引号(")。问题是TJSONString已经处理了这种情况,并最终添加了额外的转义符(\"),导致解析失败。在删除此情况后,一切正常。我已更新示例以修复此问题。 - Doug

7
我们刚遇到一个问题,我们的反斜杠没有被转义(但我们的双引号似乎被转义了,哈哈)...
解决方法是停止使用TJSONObject.ToString(),改用TJSONObject.ToJSON()。这将为您提供正确转义的字符串,而不是ToString()返回的人类可读格式。
希望这能对某些人有所帮助 :)

0
我在使用Delphi XE2时遇到了同样的问题。我的解决方案是修改库文件Data.DBXJSON.pas。我将原始文件复制到我的PatchedLibraryXE2目录中(该目录在我的库路径中排名第一)。在这里,我更改了该方法。
function TJSONString.ToString: UnicodeString;
begin
  if FStrBuffer <> nil then
//    Exit('"' + AnsiReplaceStr(FStrBuffer.ToString, '"', '\"') + '"');
    Exit('"' + EscapeValue(FStrBuffer.ToString) + '"');
  Result := NullString;
end;

我使用了Linas在上面的回答中提到的转义方法。我省略了大于127的字符的转义。为了编译,我还需要将文件Data.DBXCommon.pas复制到我的PatchedLibraryXE2目录中。现在我的TJSONObject.ToString()按预期工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接