Custom Json Serialization of class
Asked Answered
C

2

26

I have code structured like below.

public class Stats
{
        public string URL { get; set; }
        public string Status { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public int Length { get; set; }
}

and

 public class UrlStats
 {
        public string URL { get; set; }
        public int TotalPagesFound { get; set; }
        public List<Stats> TotalPages { get; set; }
        public int TotalTitleTags { get; set; }
        public List<Stats> TotalTitles { get; set; }
        public int NoDuplicateTitleTags { get; set; }
        public List<Stats> DuplicateTitles { get; set; }
        public int NoOverlengthTitleTags { get; set; }
        public List<Stats> OverlengthTitles { get; set; }
 }

Basically i am scanning a website for statistics like title tags, duplicate titles, etc.

I am using JQuery and making AJAX calls to webservice and retrieving url stats while the process is running to show user url stats by far collected since it takes quite a time to scan a big website. So after every 5 seconds i retrieve stats from server. Now the problem is all the List variable data i need to send at the end when scanning processing is complete, not during updates. What's happening right now the List<Stats> variable data is also sent during updates which is big chunk of data and i want to send only int type variables which are required to show process updates.

From searching on internet i couldn't find anything useful solving my problem and i found that Json.NET is very good library but i really don't know how to properly use it to get what i want.

Basically i am looking for serializing properties depending on their datatype at runtime, if its possible.

Cody answered 13/8, 2012 at 12:42 Comment(0)
M
42

There are two different approaches for your problem.

You should choose the first one if you are going to change your classes more often because the first approach prevents that you forget to serialize a newly added property. Furthermore it is much more reusable if you want to add another classes you want to be serialized the same way.

If you have only these two classes and it's most likely that they're not going to change you can choose the second approach to keep your solution simple.

1. Use a custom converter to select all int properties

The first approach is to use a custom JsonConverter which serializes a class or struct by only including properties which have type int. The code might look like this:

class IntPropertyConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        // this converter can be applied to any type
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // we currently support only writing of JSON
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value == null)
        {
            serializer.Serialize(writer, null);
            return;
        }

        // find all properties with type 'int'
        var properties = value.GetType().GetProperties().Where(p => p.PropertyType == typeof(int));

        writer.WriteStartObject();

        foreach (var property in properties)
        {
            // write property name
            writer.WritePropertyName(property.Name);
            // let the serializer serialize the value itself
            // (so this converter will work with any other type, not just int)
            serializer.Serialize(writer, property.GetValue(value, null));
        }

        writer.WriteEndObject();
    }
}

Then you have to decorate your class with a JsonConverterAttribute:

[JsonConverter(typeof(IntPropertyConverter))]
public class UrlStats
{
    // ...
}

Disclaimer: This code has been tested only very roughly.


2. Choose properties individually

The second solution looks a bit simpler: You can use the JsonIgnoreAttribute to decorate the attributes you want to exclude for serialization. Alternatively you can switch from "blacklisting" to "whitelisting" by explicitly including the attributes you want to serialize. Here is a bit of sample code:

Blacklisting: (I've reordered the properties for a better overview)

[JsonObject(MemberSerialization.OptOut)] // this is default and can be omitted
public class UrlStats
{
    [JsonIgnore] public string URL { get; set; }
    [JsonIgnore] public List<Stats> TotalPages { get; set; }
    [JsonIgnore] public List<Stats> TotalTitles { get; set; }
    [JsonIgnore] public List<Stats> DuplicateTitles { get; set; }
    [JsonIgnore] public List<Stats> OverlengthTitles { get; set; }

    public int TotalPagesFound { get; set; }
    public int TotalTitleTags { get; set; }
    public int NoDuplicateTitleTags { get; set; }
    public int NoOverlengthTitleTags { get; set; }
}

Whitelisting: (also reordered)

[JsonObject(MemberSerialization.OptIn)] // this is important!
public class UrlStats
{
    public string URL { get; set; }
    public List<Stats> TotalPages { get; set; }
    public List<Stats> TotalTitles { get; set; }
    public List<Stats> DuplicateTitles { get; set; }
    public List<Stats> OverlengthTitles { get; set; }

    [JsonProperty] public int TotalPagesFound { get; set; }
    [JsonProperty] public int TotalTitleTags { get; set; }
    [JsonProperty] public int NoDuplicateTitleTags { get; set; }
    [JsonProperty] public int NoOverlengthTitleTags { get; set; }
}
Macur answered 14/8, 2012 at 13:49 Comment(6)
thanks a lot, i actually used simple solution given by corrego but i will keep your solution also in mind.Cody
@DineshAhuja You should accept corrego's answer in this case.Macur
Hi i have stuck in new scenario in which i have EntityObject RacingResult which is defined in Model.Designer.cs, it has more than 15 properties. I am retrieving List<RacingResult> by searching db but want to sent only 5 properties to client. As you know i cannot put anything like [JsonProperty] in Model.Designer.cs, so is there any way i can send few properties. Thanks in advanceCody
@DineshAhuja I think the only solution for this is to use a custom converter class (like in my first approach). But you don't have to make your converter generic like this. You can create a custom converter only for your Model class. But you have then to specify your converter every time you call the serialization or deserialization function because you can't specify the converter in your Model class using the JsonConverterAttribute like I did in the last piece of code of my solution.Macur
I think there are two JSONConverters. I think @Macur shows one from Newtonsoft.Json but I think there is also one in System.Text.Json.Serialization. My .NET Core 3.1 Project wouldn't automatically perform serialization from my Controller/Action on the statement "return Json(myNET_ObjectToReturn)" unless I used the later converter which is described at: learn.microsoft.com/en-us/dotnet/standard/serialization/…Monroe
@ShawnEary The question was tagged with json.net, so yes, I wrote a converter class for the Newtonsoft library. Back in 2012, when the question was asked, JSON support from .NET Framework was pretty crappy tbh, and System.Text.Json was introduced in 2019 with .NET Core 3, so Newtonsoft was the best way to go.Macur
H
5

Oh got it, re-reading your question I think you can serialize a projection of your data.

You can try the following:

var json = JsonConvert.SerializeObject(new { u.TotalPagesFound, u.TotalTitleTags, u.NoDuplicateTitleTags, u.NoOverlengthTitleTags } );

This will convert to JSON only the int properties of your class. This is the easiest way, and it is tied to the structure of your class. If you want something more general, you will need to implement a custom converter.

Original answer:

I see no problem with your classes, you don't have anything weird like loop references, so Json.NET should have no problem serializing your class. So go grab Json.NET and then you can attempt the following

// create new instance of your url stat class
var u = new UrlStats() { URL = "a.com", TotalPages = new List<Stats>() { new Stats() { URL = "b.com", Status = "xxxx" } } };
// seralize!
var json = JsonConvert.SerializeObject(u);
Console.Write(json);

What I get with this method is something like this:

{"URL":"a.com","TotalPagesFound":0,"TotalPages":[{"URL":"b.com","Status":"xxxx","Title":null,"Description":null,"Length":0}],"TotalTitleTags":0,"TotalTitles":null,"NoDuplicateTitleTags":0,"DuplicateTitles":null,"NoOverlengthTitleTags":0,"OverlengthTitles":null}

And that looks like good json to me.

Heavyladen answered 13/8, 2012 at 14:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.