You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义EscapingHtmlConverter单元测试失败,请求排查原因

问题排查:自定义HTML转义JsonConverter单元测试返回Null

问题背景

为实现局部字符串HTML转义(而非全局转义),创建了EscapingHtmlConverter类,并通过[JsonConverter(typeof(EscapingHtmlConverter))]标记模型属性。但编写Xunit单元测试时,执行失败,实际结果为Null,怀疑ReadJson方法参数传递或逻辑存在问题。

问题代码

自定义Converter代码

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;

namespace Shared
{
    public class EscapingHtmlConverter : JsonConverter
    {
        public EscapingHtmlConverter()
        {
        }

        public override bool CanRead => true;

        public override bool CanWrite => false;

        public override bool CanConvert(Type objectType) => objectType == typeof(string);

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.String)
            {
                var res = ((string)reader.Value)?.Trim() == string.Empty ? null : ((string)reader.Value)?.Trim();

                return res == null ? null : JsonConvert.SerializeObject(
                                            res,
                                            Formatting.None,
                                            new JsonSerializerSettings
                                            { StringEscapeHandling = StringEscapeHandling.EscapeHtml });
            }

            return reader.Value;
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
}

单元测试代码

using Xunit;
using Shared;
using System;
using System.Collections.Generic;
using System.Text;
using Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Shared.Tests
{
    public class EscapingHtmlConverterTests
    {

        [Fact()]
        public void ReadJsonTest()
        {
            Model model = new Model()
            {
                PropertyToNotBeEscaped = "Something that do not need to be escaped",
                PropertyToBeEscaped = "Some string <with> [special] 'characters' & more",                
            };

            var expectedResult = JsonConvert.SerializeObject(model.PropertyToBeEscaped, Formatting.None, new JsonSerializerSettings
            {
                StringEscapeHandling = StringEscapeHandling.EscapeHtml
            });

            JsonReader reader = new JTokenReader(JToken.Parse(JsonConvert.SerializeObject(model.PropertyToBeEscaped)));
            var serializer = new JsonSerializer();
            var converter = new EscapingHtmlConverter();

            // just for debugging
            Assert.NotNull(model.PropertyToBeEscaped);
            Assert.NotNull(reader);
            Assert.NotNull(serializer);

            // Act
            var result = converter.ReadJson(reader, model.propertyTobeEscaped.GetType(), "Some string <with> [special] 'characters' & more", serializer);
            Assert.Equal(expectedResult, result?.ToString());
        }
    }
}

测试失败信息

Message: 
Assert.Equal() Failure
Expected: "Some string \u003cwith\u003e [special] \u0027characters\u0027 \u0026 more"
Actual:   (null)

问题分析与修复

1. 核心问题:JsonReader未读取任何Token

创建JTokenReader后,默认处于初始位置,没有读取任何Token,此时reader.TokenType为JsonToken.None,不会进入if (reader.TokenType == JsonToken.String)分支,直接返回reader.Value(即Null)。

2. 次要问题:属性名拼写错误

测试代码中调用model.propertyTobeEscaped.GetType(),但模型属性实际为PropertyToBeEscaped(大小写不匹配),虽然此处不直接导致Null,但属于低级错误。

3. Converter逻辑冗余(不符合需求)

原ReadJson方法中调用JsonConvert.SerializeObject会输出带双引号的转义字符串,这不符合局部转义后作为属性值的需求——实际应该直接返回转义后的字符串内容,而非序列化后的带引号结果。


修复后的代码

修复单元测试

[Fact()]
public void ReadJsonTest()
{
    Model model = new Model()
    {
        PropertyToNotBeEscaped = "Something that do not need to be escaped",
        PropertyToBeEscaped = "Some string <with> [special] 'characters' & more",                
    };

    // 修正预期:直接获取转义后的字符串(去掉序列化带来的双引号)
    var expectedResult = JsonConvert.ToString(model.PropertyToBeEscaped, StringEscapeHandling.EscapeHtml).Trim('"');

    JsonReader reader = new JTokenReader(JToken.Parse(JsonConvert.SerializeObject(model.PropertyToBeEscaped)));
    var serializer = new JsonSerializer();
    var converter = new EscapingHtmlConverter();

    // 关键:移动到第一个有效Token
    reader.Read();

    // Act:修正属性名大小写,existingValue传Null(符合JsonConverter调用逻辑)
    var result = converter.ReadJson(reader, model.PropertyToBeEscaped.GetType(), null, serializer);
    Assert.Equal(expectedResult, result?.ToString());
}

修复自定义Converter逻辑

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    if (reader.TokenType == JsonToken.String)
    {
        var rawValue = ((string)reader.Value)?.Trim();
        if (string.IsNullOrEmpty(rawValue))
        {
            return null;
        }
        // 直接转义HTML特殊字符,返回纯字符串
        return JsonConvert.ToString(rawValue, StringEscapeHandling.EscapeHtml).Trim('"');
    }

    return reader.Value;
}

关键说明

  • 必须调用reader.Read(),让Reader定位到第一个有效Token,否则无法进入字符串处理分支。
  • 避免用JsonConvert.SerializeObject处理单个字符串,否则会额外添加双引号,不符合属性值的转义需求。
  • 保持代码中属性名大小写一致,避免低级拼写错误。

内容的提问来源于stack exchange,提问作者Sasa Jovanovic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 08:15:31