解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
大石头 authored at 2018-05-15 21:21:05
11.80 KiB
X
using System.ComponentModel;
using NewLife;
using NewLife.Data;
using NewLife.Log;
using NewLife.Serialization;
using Xunit;

namespace XUnitTest.Serialization;

public class JsonWriterTests
{
    [Fact]
    public void Utc_Time()
    {
        var writer = new JsonWriter();

        var dt = DateTime.UtcNow;
        writer.Write(new { time = dt });

        var str = writer.GetString();
        Assert.NotEmpty(str);

        var js = new JsonParser(str);
        var dic = js.Decode() as IDictionary<String, Object>;
        Assert.NotNull(dic);

        var str2 = dic["time"];
        Assert.EndsWith("Z", str2 + "");
        Assert.Equal(dt.ToString("yyyy-MM-ddTHH:mm:ss") + "Z", str2);

        var dt2 = dic["time"].ToDateTime();
        Assert.Equal(DateTimeKind.Utc, dt2.Kind);
        Assert.Equal(dt.Trim(), dt2.Trim());
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void UseUtc_Setting(Boolean useUTCDateTime)
    {
        var writer = new JsonWriter { UseUTCDateTime = useUTCDateTime };

        var dt = DateTime.Now;
        writer.Write(new { time = dt });

        var str = writer.GetString();
        Assert.NotEmpty(str);

        var js = new JsonParser(str);
        var dic = js.Decode() as IDictionary<String, Object>;
        Assert.NotNull(dic);

        if (useUTCDateTime)
        {
            var str2 = dic["time"];
            Assert.EndsWith("Z", str2 + "");
            Assert.Equal(dt.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss") + "Z", str2);

            var dt2 = dic["time"].ToDateTime();
            Assert.Equal(DateTimeKind.Utc, dt2.Kind);
            Assert.Equal(dt.ToUniversalTime().Trim(), dt2.Trim());
        }
        else
        {
            var str2 = dic["time"];
            Assert.False((str2 + "").EndsWith("Z"));
            Assert.Equal(dt.ToString("yyyy-MM-ddTHH:mm:ss"), str2);

            var dt2 = dic["time"].ToDateTime();
            Assert.NotEqual(DateTimeKind.Utc, dt2.Kind);
            Assert.Equal(dt.Trim(), dt2.Trim());
        }
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void FullTime_Setting(Boolean fullTime)
    {
        var writer = new JsonWriter();
        writer.Options.FullTime = fullTime;

        var dt = DateTime.Now;
        writer.Write(new { time = dt });

        var str = writer.GetString();
        Assert.NotEmpty(str);

        var js = new JsonParser(str);
        var dic = js.Decode() as IDictionary<String, Object>;
        Assert.NotNull(dic);

        if (fullTime)
        {
            Assert.Contains("T", str);
            Assert.Contains("+", str);

            var dto = DateTimeOffset.Now;

            var str2 = dic["time"];
            // +08:00
            Assert.EndsWith(dto.Offset.ToString(), str2 + ":00");
            Assert.Equal($"{dt:yyyy-MM-ddTHH:mm:ss.fffffff}+{dto.Offset.Hours:00}:00", str2);

            var dt2 = dic["time"].ToDateTime();
            Assert.Equal(DateTimeKind.Local, dt2.Kind);
            Assert.Equal(dt.Trim(), dt2.Trim());
        }
        else
        {
            var str2 = dic["time"];
            Assert.False((str2 + "").EndsWith("Z"));
            Assert.Equal(dt.ToString("yyyy-MM-ddTHH:mm:ss"), str2);

            var dt2 = dic["time"].ToDateTime();
            Assert.Equal(DateTimeKind.Unspecified, dt2.Kind);
            Assert.Equal(dt.Trim(), dt2.Trim());
        }
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void LowerCase_Setting(Boolean lowerCase)
    {
        var writer = new JsonWriter { LowerCase = lowerCase };

        writer.Write(new { UserName = "Stone" });

        var str = writer.GetString();
        var js = new JsonParser(str);
        var dic = js.Decode() as IDictionary<String, Object>;

        var key = dic.Keys.First();
        if (lowerCase)
            Assert.Equal("username", key);
        else
            Assert.Equal("UserName", key);
        Assert.Equal("Stone", dic["UserName"]);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void CamelCase_Setting(Boolean camelCase)
    {
        var writer = new JsonWriter();
        writer.Options.PropertyNaming = camelCase ? PropertyNaming.CamelCase : PropertyNaming.None;

        writer.Write(new { UserName = "Stone" });

        var str = writer.GetString();
        var js = new JsonParser(str);
        var dic = js.Decode() as IDictionary<String, Object>;

        var key = dic.Keys.First();
        if (camelCase)
            Assert.Equal("userName", key);
        else
            Assert.Equal("UserName", key);
        Assert.Equal("Stone", dic["UserName"]);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void IgnoreNullValues_Setting(Boolean ignoreNullValues)
    {
        var writer = new JsonWriter();
        writer.Options.IgnoreNullValues = ignoreNullValues;

        writer.Write(new { Name = "", UserName = "Stone" });

        var str = writer.GetString();
        var js = new JsonParser(str);
        var dic = js.Decode() as IDictionary<String, Object>;

        var key = dic.Keys.First();
        if (ignoreNullValues)
            Assert.Equal("UserName", key);
        else
            Assert.Equal("Name", key);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void IgnoreReadOnlyProperties_Setting(Boolean ignoreReadOnlyProperties)
    {
        var writer = new JsonWriter { IgnoreReadOnlyProperties = ignoreReadOnlyProperties };

        writer.Write(new Model("Stone", "PPP"));

        var str = writer.GetString();
        var js = new JsonParser(str);
        var dic = js.Decode() as IDictionary<String, Object>;

        var key = dic.Keys.Last();
        if (ignoreReadOnlyProperties)
            Assert.Equal("Name", key);
        else
            Assert.Equal("Password", key);
    }

    class Model(String name, String pass)
    {
        public String Name { get; set; } = name;
        public String Password { get; } = pass;
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void EnumTest(Boolean enumString)
    {
        // 字符串
        var writer = new JsonWriter();
        writer.Options.EnumString = enumString;

        var data = new { Level = LogLevel.Fatal };
        writer.Write(data);

        var js = new JsonParser(writer.GetString());
        var dic = js.Decode() as IDictionary<String, Object>;
        Assert.NotNull(dic);

        if (enumString)
            Assert.Equal("Fatal", dic["Level"]);
        else
        {
            Assert.Equal(5, dic["Level"]);
            Assert.Equal((Int32)LogLevel.Fatal, dic["Level"].ToInt());
        }
    }

    [Fact]
    public void ArrayTest()
    {
        var arr = new[] { 12, 34, 56, 78 };
        var str = JsonWriter.ToJson(arr, new JsonOptions());
        Assert.Equal("[12,34,56,78]", str);
    }

    [Fact]
    public void Array_匿名()
    {
        var arr = new[] { 12, 34, 56, 78 };
        var str = JsonWriter.ToJson(arr.Select(e => e + 100), new JsonOptions());
        Assert.Equal("[112,134,156,178]", str);
    }

    [Fact]
    public void Array_DbTable()
    {
        var dt = new DbTable
        {
            Columns = new[] { "id1", "id1", "id1", "id1" },
            Rows = new List<Object[]>(),
            Total = 1234,
        };
        dt.Rows.Add(new Object[] { 12, 34, 56, 78 });
        dt.Rows.Add(new Object[] { 87, 65, 43, 32 });

        var str = JsonWriter.ToJson(dt, new JsonOptions());
        Assert.Equal("{\"Columns\":[\"id1\",\"id1\",\"id1\",\"id1\"],\"Rows\":[[12,34,56,78],[87,65,43,32]],\"Total\":1234}", str);
    }

    [Fact]
    [DisplayName("数组 null 元素必须保留(JSON 标准)")]
    public void Array_NullElements_Preserved()
    {
        var arr = new Object?[] { 1, null, 3 };
        var str = JsonWriter.ToJson(arr, new JsonOptions { IgnoreNullValues = true });
        // JSON 标准:null 是合法 JSON 值,数组中的 null 必须保留
        Assert.Equal("[1,null,3]", str);
    }

    [Fact]
    [DisplayName("数组 null 元素保留(缩进模式)")]
    public void Array_NullElements_Indented()
    {
        var arr = new Object?[] { 1, null, 3 };
        var str = JsonWriter.ToJson(arr, new JsonOptions { IgnoreNullValues = true, WriteIndented = true });
        // 缩进模式下 null 也能正常输出
        Assert.Contains("null", str);
        Assert.Contains("1", str);
        Assert.Contains("3", str);
    }

    [Fact]
    [DisplayName("数组 null 元素保留(IgnoreNullValues=false 保持原行为)")]
    public void Array_NullElements_IgnoreOff()
    {
        var arr = new Object?[] { 1, null, 3 };
        var str = JsonWriter.ToJson(arr, new JsonOptions { IgnoreNullValues = false });
        Assert.Equal("[1,null,3]", str);
    }

    [Fact]
    [DisplayName("字典键含特殊字符时正确转义")]
    public void DictionaryKey_Escaping()
    {
        var dic = new Dictionary<String, Object?>
        {
            ["key\"with\"quotes"] = 1,
            ["back\\slash"] = 2,
            ["new\nline"] = 3,
        };
        var str = JsonWriter.ToJson(dic, new JsonOptions());
        // 必须可被 JsonParser 正确解析
        var js = new JsonParser(str);
        var parsed = js.Decode() as IDictionary<String, Object>;
        Assert.NotNull(parsed);
        Assert.Equal(3, parsed.Count);
        Assert.Equal(1, parsed["key\"with\"quotes"]);
        Assert.Equal(2, parsed["back\\slash"]);
        Assert.Equal(3, parsed["new\nline"]);
    }

    [Fact]
    [DisplayName("WriteString 输出 \\b 和 \\f 简写转义")]
    public void String_Escape_Bell_FormFeed()
    {
        var writer = new JsonWriter();
        writer.Write("a\bb\fc");
        var str = writer.GetString();
        // \b (0x08) 和 \f (0x0C) 应使用简写而非 \\u0008/\\u000C
        Assert.Equal("\"a\\bb\\fc\"", str);
    }

    [Fact]
    [DisplayName("DateTime 默认使用 ISO 8601 格式")]
    public void DateTime_ISO8601()
    {
        var dt = new DateTime(2026, 6, 22, 15, 30, 0, DateTimeKind.Utc);
        var writer = new JsonWriter();
        writer.Write(dt);
        var str = writer.GetString();
        // ISO 8601: T 分隔符,UTC 用 Z 后缀(非 " UTC")
        Assert.Equal("\"2026-06-22T15:30:00Z\"", str);
    }

    [Fact]
    [DisplayName("DateTime 本地时间使用 ISO 8601 格式(无后缀)")]
    public void DateTime_ISO8601_Local()
    {
        var dt = new DateTime(2026, 6, 22, 15, 30, 0, DateTimeKind.Local);
        var writer = new JsonWriter();
        writer.Write(dt);
        var str = writer.GetString();
        // 应包含 T 分隔符
        Assert.Contains("T", str);
        // 不应包含 " UTC" 后缀
        Assert.DoesNotContain(" UTC", str);
    }

    [Fact]
    [DisplayName("IgnoreComment=false 时使用 #key 机制(合法 JSON)")]
    public void Comment_ValidJson()
    {
        var writer = new JsonWriter
        {
            IgnoreComment = false,
            Options = new JsonOptions { WriteIndented = true },
        };
        var obj = new { Name = "Stone" };
        writer.Write(obj);
        var str = writer.GetString();
        // 不应包含 // 注释(非法 JSON)
        Assert.DoesNotContain("//", str);
        // 应可被 JsonParser 正确解析
        var js = new JsonParser(str);
        var parsed = js.Decode() as IDictionary<String, Object>;
        Assert.NotNull(parsed);
        Assert.Equal("Stone", parsed["Name"]);
    }

    [Fact]
    public void UnicodeEncode()
    {
        var writer = new JsonWriter();

        writer.Write(new Model("Hello\u0001World", "智能Stone"));

        var str = writer.GetString();
        Assert.Equal("""{"Name":"Hello\u0001World","Password":"智能Stone"}""", str);

        var js = new JsonParser(str);
        var dic = js.Decode() as IDictionary<String, Object>;
        Assert.NotNull(dic);
        Assert.Equal("Hello\u0001World", dic["Name"]);
        Assert.Equal("智能Stone", dic["Password"]);
    }
}