如何将TJsonString转换为包含JSON的字符串?

问题描述 投票:0回答:1

给定一个 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 字符串?

研究工作

尝试 #1 -
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

尝试 #2 -
TJsonObject.ToJson()

虽然

ToString()
旨在返回人类可读的文本,但
ToJSON()
旨在返回有效的 JSON。

唯一的问题是它在Delphi XE6中不存在。

继续前进!

尝试 #3 -
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 转义规则:

    #$D ->
  • \r
    
    
  • #$A ->
  • \n
    
    
  • #$9 ->
  • \t
    
    
  • \
     -> 
    leave as-is
    
    
  • "
     -> 
    leave as-is
    
    
  • /
     -> 
    leave as-is
    
    
但是我不知道#32下的其他控制字符会发生什么。

尝试 #5 - 访问

TJsonString
 受保护 
TStringBuilder

该对象维护 json 中实际

字符的受保护缓冲区:

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字符串?

奖励阅读

  • 如何使用 DBXJSON 将字符串与带有转义/特殊字符的 JSON 相互转换?
json delphi delphi-xe6
1个回答
1
投票
解决方案是自己推出。

首先是主要功能

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;
奖金喋喋不休

Delphi 的解析器也有一个众所周知的错误,无法解析有效的 JSON。

您需要通过正则表达式搜索替换运行 json 字符串:

(\[\[\s\]*\])(?=(?:\[^"\]|"\[^"\]*")*$)

[]

© www.soinside.com 2019 - 2024. All rights reserved.