注意:我熟悉SuperObject,但我的要求是使用DBXJSON单元.
当通过ToString()方法返回JSON表示时,TJSONString看起来不正确地转义.
什么(如果有的话)我做错了,如何正确地将一个字符串转换为/从正确的JSON表示的特殊字符?
也许我错过了一些事情,但是以下问答似乎都没有直接解决这个问题:
> Delphi decode json/utf8 escaped text
> Delphi JSON library for XE2 available for object serialization
> Delphi: JSON array
> How to parse nested JSON object in Delphi XE2?
> Nested json object deserializing using Delphi 2012
EDIT:
As it turns out,the examples below were indeed working as expected.
What wasn’t clear to me was that when creating a TJSONString via it’s constructor and adding it to a TJSONObject,the ToString() method will return an escaped representation. However,after parsing a TJSONObject,the ToString() method will returned the un-escaped representation.
The only other caveat was that the EscapeString() function in the sample code below was handling the double-quote. Although I wasn’t using the double quote here,some of my other code was,and that caused the parsing to fail because TJSONString already escapes that character. I’ve updated my sample code to remove this handling from the EscapeString() function,which is what I’ve been using in my own classes.
Thanks again to @Linas for the answer,which helped me to “get” it.
原始字符串值:
Text := 'c:\path\name' +#13 + #10 + 'Next Line'; Text: c:\path\name Next Line
什么DBXJSON生产(NO ESCAPES):
JsonString: "c:\path\name Next Line" JsonPair: "MyString":"c:\path\name Next Line" JsonObject: {"MyString":"c:\path\name Next Line"}
解析UN-escaped文本失败:
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"}
解析ESCAPED文本(INVALID)(要解析的文本为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: ',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.
解决方法
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,AIndex + 6,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',ix); end; #9: begin AddChars('\t',ix); end; #10: begin AddChars('\n',ix); end; #12: begin AddChars('\f',ix); end; #13: begin AddChars('\r',ix); end; #0 .. #7,#11,#14 .. #31: begin AddUnicodeChars('\u' + IntToHex(Word(AChar),4),ix); end else begin if Word(AChar) > 127 then begin AddUnicodeChars('\u' + IntToHex(Word(AChar),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;