How to make JSON.Net serializer to call ToString() when serializing a particular type?
Asked Answered
H

4

36

I am using Newtonsoft.Json serializer to convert C# classes to JSON. For some classes I don't need the serializer to an instance to individual properties, but instead just call ToString on the object, i.e.

public class Person
{
   public string FirstName { get; set; }
   public string LastName { get; set; }

   public override string ToString() { return string.Format("{0} {1}", FirstName, LastName ); }
}

What should I do to get the Person object serialized as the result of its ToString() method? I may have many classes like this, so I don't want to end up with a serializer specific for Person class, I want to have one than can be applicable to any classe (via attribute I guess).

Hispanicism answered 12/3, 2014 at 14:38 Comment(0)
B
71

You can do this easily with a custom JsonConverter:

public class ToStringJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteValue(value.ToString());
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

To use the converter, decorate any classes that need to be serialized as string with a [JsonConverter] attribute like this:

[JsonConverter(typeof(ToStringJsonConverter))]
public class Person
{
    ...
}

Here is a demo showing the converter in action:

class Program
{
    static void Main(string[] args)
    {
        Company company = new Company
        {
            CompanyName = "Initrode",
            Boss = new Person { FirstName = "Head", LastName = "Honcho" },
            Employees = new List<Person>
            {
                new Person { FirstName = "Joe", LastName = "Schmoe" },
                new Person { FirstName = "John", LastName = "Doe" }
            }
        };

        string json = JsonConvert.SerializeObject(company, Formatting.Indented);
        Console.WriteLine(json);
    }
}

public class Company
{
    public string CompanyName { get; set; }
    public Person Boss { get; set; }
    public List<Person> Employees { get; set; }
}

[JsonConverter(typeof(ToStringJsonConverter))]
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public override string ToString() 
    { 
        return string.Format("{0} {1}", FirstName, LastName); 
    }
}

Output:

{
  "CompanyName": "Initrode",
  "Boss": "Head Honcho",
  "Employees": [
    "Joe Schmoe",
    "John Doe"
  ]
}

If you also need to be able to convert from string back to an object, you can implement the ReadJson method on the converter such that it looks for a public static Parse(string) method and calls it. Note: be sure to change the converter's CanRead method to return true (or just delete the CanRead overload altogether), otherwise ReadJson will never be called.

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    MethodInfo parse = objectType.GetMethod("Parse", new Type[] { typeof(string) });
    if (parse != null && parse.IsStatic && parse.ReturnType == objectType)
    {
        return parse.Invoke(null, new object[] { (string)reader.Value });
    }

    throw new JsonException(string.Format(
        "The {0} type does not have a public static Parse(string) method that returns a {0}.", 
        objectType.Name));
}

Of course, for the above to work, you will also need to make sure to implement a suitable Parse method on each class you're converting, if it doesn't already exist. For our example Person class shown above, that method might look something like this:

public static Person Parse(string s)
{
    if (string.IsNullOrWhiteSpace(s))
        throw new ArgumentException("s cannot be null or empty", "s");

    string[] parts = s.Split(new char[] { ' ' }, 2);
    Person p = new Person { FirstName = parts[0] };
    if (parts.Length > 1)
        p.LastName = parts[1];

    return p;
}

Round-trip demo: https://dotnetfiddle.net/fd4EG4

Barmy answered 12/3, 2014 at 15:9 Comment(2)
@BrianRogers Thanks for a fantastic answer. I was wondering if you can give any insight as to how to implement ReadJson() in your converter such that it would deserialize that string back into its two respective properties?Siding
I found this wasn't working for me at first, but it was because my ToString() method has decorated with 'new' rather than 'override' (no idea why) - once I corrected that it worked fine. Thanks for sharing.Pollen
M
12

There is a quicker way of doing this if it is not intended to be used on a large scale, in the example below, it is done for the RecordType property

[JsonIgnore]
public RecordType RecType { get; set; }

[JsonProperty(PropertyName = "RecordType")]
private string RecordTypeString => RecType.ToString();
Mordred answered 14/2, 2018 at 17:2 Comment(0)
D
1

You can simply try Newtonsoft's JSON builder library and Serilaize the object of type Person using such code:

Dictionary<string, object> collection = new Dictionary<string, object>()
    {
      {"First", new Person(<add FirstName as constructor>)},
      {"Second", new Person(<add LastName as constructor>)},

    };
string json = JsonConvert.SerializeObject(collection, Formatting.Indented, new JsonSerializerSettings
  {
    TypeNameHandling = TypeNameHandling.All,
    TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple
  });
Duhon answered 12/3, 2014 at 15:8 Comment(1)
Thanks. I went for an approach suggested by Brian (using custom converter).Hispanicism
G
-2

I don't have time to test my solution but it should work. Assuming all of the classes you are using are your own, why don't you just do a ToString override on all of them and the classes that need to use the Newtonsoft.Json serializer can just be serialized within the ToString method and returned. This way, you can always just call ToString when you want to get the serialized string of the object.

Guaiacum answered 12/3, 2014 at 14:42 Comment(1)
First, I don't want to mix ToString and Serialize. They have different purposes. Second, I can have complex object graphs so I should rely on the serializer to travers the graph and apply the serialization algorithm. With your appoach this won't work.Hispanicism

© 2022 - 2024 — McMap. All rights reserved.