在.NET中控制自定义类型的JSON序列化格式

3
我有一个PhoneNumber类,它存储了一个规范化的字符串,并且我已经定义了string <-> Phone的隐式操作符,以简化处理PhoneNumber作为字符串的方式。我还重写了ToString()方法,始终返回清理后的号码版本(没有连字符、括号或空格)。在任何显示号码的视图中,我都会显式调用phone.Format()。
问题在于序列化具有PhoneNumber的实体到JSON;JavaScriptSerializer将其序列化为[object Object]
我希望将其序列化为(555)555-5555格式的字符串。
我考虑编写自定义JavaScriptConverter,但JavaScriptConverter.Serialize()方法返回一个名称-值对字典。我不想将PhoneNumber视为具有字段的对象,我只想将其序列化为字符串。
2个回答

1

值得考虑你想要的JSON。

假设你有这个类

class Person
{
    public string Name { get; set; }
    public PhoneNumber HomePhone { get; set; }
}

你想将它序列化为JSON格式,像这样

{ "Name":"Joe", "HomePhone": "555-555-555" }

但是你会得到JSON,类似这样的内容

{ "Name":"Joe","HomePhone": {"Number": "555-555-555"} }

--

为了理解这一点,考虑到Person的属性Number是一个对象。JSON将期望至少有一个{}来包装该对象 - 或者更准确地说,在{}内部有一组名称/值。
如果您真的非常想要前面的JSON语法,您需要为Person对象注册自定义转换器,以便您可以说服它将其序列化和反序列化为字符串(请参见下面的代码示例)。
然而,我建议您接受因为PhoneNumber是一个对象,所以当序列化为JSON时,它对应于名称/值字典,并接受JSON看起来可能不太“干净”的事实。
以下是一个代码示例,可以实现您最初的目标(不推荐使用)。
class Person
{
    public string Name { get; set; }
    public PhoneNumber HomeNumber { get; set; }
}

struct PhoneNumber
{
    private string _number;
    public PhoneNumber(string number)
    {
        _number = number;
    }

    public override string ToString()
    {
        return _number;
    }
}

class PersonConverter : JavaScriptConverter
{
    public override IEnumerable<Type> SupportedTypes
    {
        get { yield return typeof(Person); }
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        Person person = obj as Person;
        if (person != null)
        {
            Dictionary<string, object> dict = new Dictionary<string, object>();
            dict["Name"] = person.Name;
            dict["HomeNumber"] = person.HomeNumber.ToString();
            return dict;
        }
        return new Dictionary<string, object>();
    }

    public override object Deserialize(IDictionary<string, object> dict, Type type, JavaScriptSerializer serializer)
    {
        if (dict == null)
            throw new ArgumentNullException("dict");

        if (type == typeof(Person))
        {
            // Deserialize the Person's single property.
            string name = (string)dict["Name"];
            string homeNumber = (string)dict["HomeNumber"];

            // Create the instance to deserialize into.
            Person person = new Person()
            {
                Name = name,
                HomeNumber = new PhoneNumber(homeNumber)
            };
            return person;
        }
        return null;
    }
}

class Program
{
    static void Main(string[] args)
    {
        PhoneNumber number = new PhoneNumber("555 555");
        Person joe = new Person() { Name = "Joe", HomeNumber = number };

        JavaScriptSerializer serializer = new JavaScriptSerializer();
        serializer.RegisterConverters(new JavaScriptConverter[] { new PersonConverter() });
        Console.Out.WriteLine(serializer.Serialize(joe));
    }
}

我知道我想要的JSON。电话号码需要像字符串一样处理。它们需要成为简单属性,而不是聚合体。你上面写的代码序列化了Person类,而不是PhoneNumber类。这是我的问题的关键所在。我实现了一个JavaScriptConverter,但由于我必须返回一个名称-值对字典,我不确定如何只返回一个字符串。我尝试将“this”作为字典中的键返回,但它不起作用。我不想为系统中每个具有PhoneNumber字段的类创建自定义转换器,我需要一个针对PhoneNumber本身的转换器。 - codenheim
我指的是考虑你想要的JSON以了解问题的原因。JSON的本质是将对象表示为一组键/值对。如果你想将PhoneNumber类视为基元类型,那么从该类的自定义转换器中无法实现这一点,因为JSON的本质是假定比字符串/整数更复杂的每个对象都表示为一组键/值对。 - user277195
这就是我提问的重点。你的回答重复了我已经考虑过(实际上已经尝试过)的内容;尽管我很感激你的好意,但你告诉我我已经知道的东西。我无法从一个密封类型(字符串)中派生出来,所以我尝试重写ToString()并定义一个字符串转换运算符,但它没有满足我的需求。在面向对象的层面上,似乎有一个更简单的解决方案。 - codenheim

0

我知道这已经过时了,但在搜索答案时,我遇到了this。那里提供的答案是创建一个命名类型,该类型继承自URI并实现IDictionary接口。当它被序列化时,.Net会识别URI继承关系并输出字符串而不是对象集合。这将使您拥有一个几乎毫无用处的大型类,仅用于传递字典超出重写序列化方法的目的。

如果其他人有一种方法可以在不使用额外类和无用方法的情况下完成此操作,那就太好了。否则,这是一种“方法”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接