给定一个 TJsonString 对象,我如何获得 JSON 的字符串表示形式?
给出以下示例(有效)JSON:
{
"comment":"The quick bröwn fox\r\n\tjumped \"over\" the \\lazy\/ dog\r\nthen 💩 on a log."
}
在可读文本中,
comment
看起来像:
The quick bröwn fox
jumped "over" the \lazy/ dog
then 💩 on a log.
我们将这个 JSON 解析为一个对象:
o: TJsonObject;
o := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(JsonStr), 0, True) as TJsonObject;
内存中有一个 JSON 对象。现在我们的目标是将其转换为 JSON。
如何让 Delphi XE6 返回相应的有效 JSON 字符串?
TJsonObject.ToString()
来电:
o.ToString
返回(无效)JSON:
{"comment":"The quick bröwn fox↵
jumped \"over\" the \lazy/ dog↵
then 💩 on a log."}
JSON 无效,因为:
CR
进入 \r
LF
进入 \n
\
进入 \\
/
进入 \/
这是有道理的,因为
ToString()
旨在 返回人类可读的文本;无效的 JSON
TJsonObject.ToJson()
虽然
ToString()
旨在返回人类可读的文本,但 ToJSON()
旨在返回有效的 JSON。
唯一的问题是它在Delphi XE6中不存在。
继续前进!
TJsonObject.ToBytes()
使用
ToBytes()
方法填充字节数组:
// Allocate a buffer to hold the JSON
buffer: TBytes;
SetLength(buffer, o.EstimatedByteSize);
// Populate the buffer, and size it to its actual length
n: Integer;
n := o.ToBytes(buffer, 0); // fill the byte array buffer
SetLength(buffer, n); // size buffer to final size
// Copy byte array to RawByteString so we can see it
s: RawByteString;
SetLength(s, n); // size the raw byte string
Move(buffer[0], s[1], n); // fill the raw byte string
返回以下 JSON:
{"comment":"The quick br\u00F6wn fox\r\n\tjumped \"over\" the \\lazy\/ dog\r\nthen \uD83D\uDCA9 on a log."}
虽然这在技术上会生成有效的 JSON,但它不必要地转义了 #127 以上的任何代码点。不理想,不是我想要的,也不是我要求的,因为 JSON 中的字符串允许包含“Unicode 字符”。
并且,要解决这一切,我必须做的是:编写一个 JSON 解析器,然后编写将 JSON 转换为字符串的代码。这就是我要问的问题。尝试 #4 - 修复
ToString()
TJsonString.ToString()
将返回一个几乎有效的JSON字符串:
"The quick bröwn fox#$D#$A
#9'jumped \"over\" the lazy dog#$D#$A
then 💩 on a log."
我可以看到它非常接近是有效的 JSON。我们只需要应用 some JSON 转义规则:
\r
\n
\t
\
->
leave as-is
"
->
leave as-is
/
->
leave as-is
尝试 #5 - 访问
TJsonString
受保护
TStringBuilder
字符的受保护缓冲区:
TJSONString = class(TJSONValue)
protected
FStrBuffer: TStringBuilder;
这些字符是它们应该在内存中的实际字符:
'T', 'h', 'e', ' ', 'q', 'u', 'i', 'c', 'k', ' ', 'b', 'r', 'ö', 'w', 'n', ' ', 'f', 'o', 'x', #$D, #$A,
#9, 'j', 'u', 'm', 'p', 'e', 'd', ' ', '"', 'o', 'v', 'e', 'r', '"', ' ', 't', 'h', 'e', ' ', '\', 'l', 'a', 'z', 'y', '/', ' ', 'd', 'o', 'g', #$D, #$A,
't', 'h', 'e', 'n', ' ', #$D83D, '�', ' ', 'o', 'n', ' ', 'a', ' ', 'l', 'o', 'g', '.', #0
所以,如果我能到达TJsonString
内部,我就可以吸出正确字符:
type
TJsonStringFriend = class(TJsonString);
var
s: UnicodeString;
s := TJsonStringFriend(AJsonString).FStrBuffer.ToString;
它返回字符串的 UnicodeString
应该包含的内容:
The quick bröwn fox'#$D#$A#9'jumped "over" the \lazy/ dog'#$D#$A'then 💩 on a log.
现在,我所要做的就是将字符串转义回 JSON:
var
i: Integer;
ch: WideChar;
for i := 1 to Length(s) do
begin
ch := s[i];
case ch of
'"': Result := Result + '\"';
'\': Result := Result + '\\';
'/': Result := Result + '\/';
#$8: Result := Result + '\b';
#$c: Result := Result + '\f';
#$a: Result := Result + '\n';
#$d: Result := Result + '\r';
#$9: Result := Result + '\t';
else
if (ch < WideChar(32)) then
Result := Result + '\u'+IntToHex(Ord(ch), 4)
else
Result := Result + ch;
end;
end;
但是,为了使用它,我们必须做一切:
function JsonValueToJSON(JsonValue: TJSONValue; Indentation: string=''): UnicodeString;
var
jsonArray: TJSONArray;
jsonObject: TJSONObject;
pair: TJSONPair;
i: Integer;
s: UnicodeString;
ch: WideChar;
begin
if JsonValue is TJSONArray then
begin
jsonArray := JsonValue as TJSONArray;
Result := '[' + sLineBreak;
for i := 0 to jsonArray.Count-1 do
begin
Result := Result + Indentation + ' ' + JsonValueToJSON(jsonArray.Items[i], Indentation + ' ');
if i < jsonArray.Count-1 then
Result := Result + ',';
Result := Result + sLineBreak;
end;
Result := Result + Indentation + ']';
end
else if JsonValue is TJSONObject then
begin
jsonObject := JsonValue as TJSONObject;
Result := '{' + sLineBreak;
for i := 0 to jsonObject.Count-1 do
begin
pair := jsonObject.Pairs[i];
Result := Result + Indentation+' "'+pair.JsonString.Value+'": '+JsonValueToJSON(pair.JsonValue, Indentation+ ' ');
if i < jsonObject.Count-1 then
Result := Result + ',';
Result := Result + sLineBreak;
end;
Result := Result + Indentation + '}';
end
else if JsonValue is TJsonString then
begin
// Delphi doesn't know how to emit valid JSON; we'll do it ourselves
Result := '"';
//s := (JsonValue as TJsonString).ToString;
s := TJsonStringFriend(JsonValue as TJsonString).FStrBuffer.ToString;
for i := 1 to Length(s) do
begin
ch := s[i];
case ch of
'"': Result := Result + '\"';
'\': Result := Result + '\\';
'/': Result := Result + '\/';
#$8: Result := Result + '\b';
#$c: Result := Result + '\f';
#$a: Result := Result + '\n';
#$d: Result := Result + '\r';
#$9: Result := Result + '\t';
else
if (ch < WideChar(32)) then
Result := Result + '\u'+IntToHex(Ord(ch), 4)
else
Result := Result + ch;
end;
end;
Result := Result+'"';
end
else
begin
// JsonValue is TJSONNumber, TJSONTrue, TJSONFalse, TJSONNull
// I trust those know how to serialize themselves correctly into JSON
Result := JsonValue.ToString;
end;
end;
除了,现在我处于一种情况,我希望我全部正确。
这肯定不是我们想要的吗? 6 小时的System.JSON
内部细节的兔子洞,并且必须重新发明轮子。总结
TJsonValue
转换为JSON字符串?奖励阅读
首先是主要功能
class function TJsonHelper.ToJSON(const AJsonValue: TJsonValue): UnicodeString;
begin
{
We have to do this most obvious thing ourselves, since Delphi gets it wrong.
//WRONG: returns a human-readable string, but invalid JSON (e.g. does not convert CRLF into \r\n)
Result := o.ToString;
//INVALID: not defined in XE6
Result := o.ToJSON;
//WRONG: encodes everything above 128 into escaped \u0083
SetLength(buffer, o.EstimatedByteSize);
n := o.ToBytes(buffer, 0);
SetLength(buffer, n);
}
if AJsonValue = nil then
begin
Result := '';
Exit;
end;
Result := PrettifyJsonValue(AJsonValue, '');
end;
私人帮手功能PrettifyJsonValue() 辅助函数中是私有的:
type
// Crack open the JsonString, and feast on the tasty string builder inside.
TJsonStringFriend = class(TJsonString)
end;
function PrettifyJsonValue(JsonValue: TJSONValue; Indentation: string=''): UnicodeString;
var
jsonArray: TJSONArray;
jsonObject: TJSONObject;
pair: TJSONPair;
i: Integer;
s: UnicodeString;
ch: WideChar;
sKey, sValue: UnicodeString;
begin
TConstraints.NotNull(JsonValue);
if JsonValue is TJSONArray then
begin
jsonArray := JsonValue as TJSONArray;
// Workaround a Delphi System.JSON bug where it cannot parse an empty array such as:
// "cities": [ ]
if jsonArray.Count <= 0 then
begin
Result := '[]';
Exit;
end;
Result := '['+ sLineBreak;
for i := 0 to jsonArray.Count-1 do
begin
Result := Result + Indentation + ' ' + PrettifyJsonValue(jsonArray.Items[i], Indentation + ' ');
if i < jsonArray.Count-1 then
Result := Result + ',';
Result := Result + sLineBreak;
end;
Result := Result + Indentation + ']';
end
else if JsonValue is TJSONObject then
begin
jsonObject := JsonValue as TJSONObject;
Result := '{' + sLineBreak;
for i := 0 to jsonObject.Count-1 do
begin
pair := jsonObject.Pairs[i];
sKey := pair.JsonString.Value;
sValue := PrettifyJsonValue(pair.JsonValue, Indentation+' ');
Result := Result + Indentation+' "'+sKey+'": '+sValue;
if i < jsonObject.Count-1 then
Result := Result + ',';
Result := Result + sLineBreak;
end;
Result := Result + Indentation + '}';
end
else if JsonValue.ClassType = TJsonString then //TJsonNumber descends from TJsonString
begin
// Delphi doesn't know how to emit valid JSON; we'll do it ourselves
//SURPRISE: TJsonNumber descends from TJsonString. No, i'm not joking. Not even a little.
Result := '"';
// s := (JsonValue as TJsonString).ToString;
s := TJsonStringFriend(JsonValue as TJsonString).FStrBuffer.ToString;
for i := 1 to Length(s) do
begin
ch := s[i];
case ch of
'"': Result := Result + '\"';
'\': Result := Result + '\\';
'/': Result := Result + '\/';
#$8: Result := Result + '\b';
#$c: Result := Result + '\f';
#$a: Result := Result + '\n';
#$d: Result := Result + '\r';
#$9: Result := Result + '\t';
else
if (ch < WideChar(32)) then
Result := Result + '\u'+IntToHex(Ord(ch), 4)
else
Result := Result + ch;
end;
end;
Result := Result+'"';
end
else
begin
// JsonValue is TJSONNumber, TJSONTrue, TJSONFalse, TJSONNull
// I trust those know how to serialize themselves correctly into JSON
Result := JsonValue.ToString;
end;
end;
奖金喋喋不休您需要通过正则表达式搜索替换运行 json 字符串:
(\[\[\s\]*\])(?=(?:\[^"\]|"\[^"\]*")*$)
[]