取消转义后如何转义嵌入的 JSON
Posted
技术标签:
【中文标题】取消转义后如何转义嵌入的 JSON【英文标题】:How to escape embedded JSON after unescape 【发布时间】:2019-01-21 13:04:11 【问题描述】:当使用 Json.NET 进行序列化时,我需要在反序列化之前取消转义后转义嵌入的 JSON。这意味着我根据this 帖子未转义 JSON。
这是我的 JSON:
"Message":null,
"Error":false,
"VData":
"RNumber":null,
"BRNumber":"Session1"
,
"onlineFields":
"CCode":"Web",
"MNumber":"15478655",
"Product":"100",
"JsonFile":"
\"evaluation\":
\"number\":[
\"@paraID\":\"1000\",
\"@Value\":\"\",
\"@label\":\"We are america\"
,
\"@paraID\":\"2000\",
\"@Value\":\"100\",
\"@label\":\"We are japan\"
,
\"@paraID\":\"3000\",
\"@Value\":\"1000\",
\"@label\":\"We are UK\"
,
\"@paraID\":\"4000\",
\"@Value\":\"\",
\"@label\":\"We are China\"
]
"
取消转义后,我将上面的 JSON 绑定到我的模型类。它工作正常。将 JSON 绑定到我使用以下代码的模型。
private static void showJSON(string testJson)
Response response = JsonConvert.DeserializeObject<Response>(testJson);
var dropdowns = response.OnlineFields.JsonFile;
string json = JsonConvert.SerializeObject(dropdowns, Newtonsoft.Json.Formatting.Indented);
Console.WriteLine(json);
将 JSON 绑定到模型后,有一些逻辑可以将值设置为 JSON 并返回未转义的 JSON。这意味着它还返回未转义的JsonFile
,我再次需要上面的 JSON 格式(转义嵌入的JsonFile
)发送到客户端 API。
这是未转义的 JSON 格式,我需要将其转换为上面的转义 JSON(转义嵌入 JsonFile
)
"Message":null,
"Error":false,
"VData":
"RNumber":null,
"BRNumber":"Session1"
,
"onlineFields":
"CCode":"Web",
"MNumber":"15478655",
"Product":"100",
"JsonFile":
"evaluation":
"number":[
"@paraID":"1000",
"@Value":"",
"@label":"We are america"
,
"@paraID":"2000",
"@Value":"100",
"@label":"We are japan"
,
"@paraID":"3000",
"@Value":"1000",
"@label":"We are UK"
,
"@paraID":"4000",
"@Value":"",
"@label":"We are China"
]
之前我问过question 如何将这种嵌入的 JSON 直接反序列化为 c# 类,但那里的答案没有解释如何以相同的格式重新序列化。我需要将上一个问题的答案扩展到写作。
【问题讨论】:
【参考方案1】:您可以通过覆盖JsonConverter.WriteJson()
并执行嵌套序列化,将EmbeddedLiteralConverter<T>
从this answer 扩展到How do I convert an escaped JSON string within a JSON object?,然后编写生成的字符串文字,例如所以:
public class EmbeddedLiteralConverter<T> : JsonConverter
public override bool CanConvert(Type objectType)
return typeof(T).IsAssignableFrom(objectType);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
using (new PushValue<bool>(true, () => Disabled, (canWrite) => Disabled = canWrite))
using (var sw = new StringWriter(writer.Culture))
// Copy relevant settings
using (var nestedWriter = new JsonTextWriter(sw)
DateFormatHandling = writer.DateFormatHandling,
DateFormatString = writer.DateFormatString,
DateTimeZoneHandling = writer.DateTimeZoneHandling,
StringEscapeHandling = writer.StringEscapeHandling,
FloatFormatHandling = writer.FloatFormatHandling,
Culture = writer.Culture,
// Remove if you don't want the escaped \r\n characters in the embedded JSON literal:
Formatting = writer.Formatting,
)
serializer.Serialize(nestedWriter, value);
writer.WriteValue(sw.ToString());
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled get return disabled; set disabled = value;
public override bool CanWrite get return !Disabled;
public override bool CanRead get return !Disabled;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
if (reader.TokenType == JsonToken.Null)
return null;
var contract = serializer.ContractResolver.ResolveContract(objectType);
if (contract is JsonPrimitiveContract)
throw new JsonSerializationException("Invalid type: " + objectType);
if (existingValue == null)
existingValue = contract.DefaultCreator();
if (reader.TokenType == JsonToken.String)
var json = (string)JToken.Load(reader);
using (var subReader = new JsonTextReader(new StringReader(json)))
// By populating a pre-allocated instance we avoid an infinite recursion in EmbeddedLiteralConverter<T>.ReadJson()
// Re-use the existing serializer to preserve settings.
serializer.Populate(subReader, existingValue);
else
serializer.Populate(reader, existingValue);
return existingValue;
struct PushValue<T> : IDisposable
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
#region IDisposable Members
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
if (setValue != null)
setValue(oldValue);
#endregion
然后,在反序列化和序列化时将转换器添加到JsonSerializerSettings.Converters
:
var settings = new JsonSerializerSettings
Converters = new EmbeddedLiteralConverter<JsonFile>() ,
;
var response = JsonConvert.DeserializeObject<Response>(testJson, settings);
var json2 = JsonConvert.SerializeObject(response, Formatting.Indented, settings);
或者,您可以使用 JsonConverterAttribute
将转换器直接应用于您的模型,如下所示:
public class OnlineFields
public string CCode get; set;
public string MNumber get; set;
public string Product get; set;
[JsonConverter(typeof(EmbeddedLiteralConverter<JsonFile>))]
public JsonFile JsonFile get; set;
注意事项:
严格来说,您的输入 JSON 格式不正确。属性JsonFile
的字符串值包含未转义的回车符:
"JsonFile":"
\"evaluation\":
\"number\":[
根据original JSON proposal 和JSON RFC 7159 Page 8 这样的控制字符必须转义:
"\r\n \"evaluation\": \r\n \"number\": ..."
要确认这一点,您可以将初始 JSON 上传到 https://jsonformatter.curiousconcept.com/,它会报告以下错误:
无效的 JSON (RFC 4627):错误:发现无效字符。[代码 18,结构 39]
事实证明,Json.NET 将毫无怨言地读取此类无效 JSON,但只会通过正确转义嵌套 JSON 文字内的回车和换行符来编写格式正确的 JSON。因此,您重新序列化的 JSON 看起来与初始 JSON 不同。但是,它将是格式良好的,并且应该可以被任何 JSON 解析器使用。
为防止序列化时出现堆栈溢出异常,EmbeddedLiteralConverter<T>.WriteJson()
在使用从this answer 到JSON.Net throws ***Exception when using [JsonConvert()] 的技术递归调用时会禁用自身。
工作示例 .Net fiddle here.
【讨论】:
谢谢您,先生。 :) 你解释得很好:) 再次感谢你的努力和友好的回应:)以上是关于取消转义后如何转义嵌入的 JSON的主要内容,如果未能解决你的问题,请参考以下文章
python常用转义字符串总结:各种字符转义的不同如何取消转义字符效果?