views:

60

answers:

1

I have a PhoneNumber class that stores a normalized string, and I've defined implicit operators for string <-> Phone to simplify treatment of the PhoneNumber as a string. I've also overridden the ToString() method to always return the cleaned version of the number (no hyphens or parentheses or spaces). In any views where I display the number, I explicitly call phone.Format().

The problem here is serializing an entity that has a PhoneNumber to JSON; JavaScriptSerializer serializes it as [object Object].

I want to serialize it as a string in (555)555-5555 format.

I've looked at writing a custom JavaScriptConverter, but JavaScriptConverter.Serialize() method returns a dictionary of name-value pairs. I do not want PhoneNumber to be treated as an object with fields, I want to simply serialize it as a string.

+1  A: 

It's worth considering the JSON you want.

Assuming you have this class

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

You want this serialized to JSON, like this

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

But you are getting JSON, something like this

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

--

To see why this is so, consider that the property Number of Person is an object. JSON is going to expect at least a {} to wrap the object - or more to the point, a set of name/values within the {}.

If you really, really want the former JSON syntax you need to register a custom converter for the Person object so that you can convince it to serialize and deserialze as a string. (see code example below).

However, I'd recommend that you just accept that because PhoneNumber is an object it correspond to a name/value dictionary when serialized into JSON and accept that the JSON is going to look a little less 'clean' than you might ideally want.

FWIW here's a code example that achieves your original aim (not the recommended approach)..

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));
    }
}
I know the JSON I want. Phone numbers need to be treated like strings. They need to be simple attributes, not aggregates. The code you wrote above is serializing the Person class, not the PhoneNumber class. That is the crux of my question. I implemented a JavaScriptConverter, but since I have to return a dictionary of name-value pairs, I'm not sure how to return just a string. I tried returning "this" as a key in the dictionary, but it doesn't work. I do not want a custom converter for every class in my system that has a PhoneNumber field, I need a converter for PhoneNumber itself.
mrjoltcola
i meant only its worth considering the JSON you want in order to understand the cause of your problem.. it is the nature of JSON to represent objects as a set of key/value pairs.. you want to treat your PhoneNumber class as a primitive, its just not possible to do that from *within* the custom convertor for *that class* because it is the nature of JSON to assume every object of more complexity than string/int is represented as a set of key/value pairs.
That is the point of my question. Your answer repeats what I stated I had already considered (tried actually); as much as I appreciate your kind intentions, you are telling me what I already know. I cannot derive from a sealed type (string) so I tried overriding ToString() and defining a string cast operator but it has not filled the need. It would seem there is a simpler solution, at an OO level, for example.
mrjoltcola