Search code examples
c#arraysjsonperformancesubtraction

c# Subtract one json's property int value from another based on another property's same value


I have two jsons data: Json1:

[
{"name":"a1", "quantity": 10 },
{"name":"a2", "quantity": 11 },
{"name":"a3", "quantity": 12 },
{"name":"a4", "quantity": 13 },
{"name":"a5", "quantity": 14 },
]

Json2:

[
{"name":"a1", "quantity": 11 },
{"name":"b1", "quantity": 1 },
{"name":"b2", "quantity": 12 },
{"name":"a3", "quantity": 13 },
{"name":"a5", "quantity": 14 },
]

I want Json1's quantity subtract Json2's quantity based on same "name"

The result will return all the Json1's items plus another property "differ".

[
{"name":"a1", "quantity": 10, "differ": -1 },
{"name":"a2", "quantity": 11, "differ": 11  }, // "a2" is not in Json2, so "differ" will be 11
{"name":"a3", "quantity": 12, "differ": -1 },
{"name":"a4", "quantity": 13, "differ": 13 },
{"name":"a5", "quantity": 14, "differ": 0 },
]

It takes about 2 seconds for input data(Json1 and Json2) have more than 3000-5000 items for each when using the solution below.Looking for a new solution with BETTER performance. Say less than 1 second with around 5000 items.
C# Code:

public string GetDiff(string json1, string json2)
{
            var json1Array = JArray.Parse(json1);
            var json2Array = JArray.Parse(json2);
            var json3Array = new JArray();
            foreach (var item in json1Array)
            {
                var name = (string) item["name"];
                var quantity = (int) item["quantity"];
                var differ = quantity;
                var itemJson2 = json2Array.Where(it => (string) it["name"] == name).FirstOrDefault();
                if (itemJson2 != null)
                {
                    differ = quantity - (int) itemJson2["quantity"];
                }
                json3Array.Add(new JObject() { { "name", name }, { "quantity", quantity }, { "differ", differ } });
            }
            result = JsonConvert.SerializeObject(json3Array);

}

Solution

  • I've tested it using your method and a different method using dictionaries. You can find it as a dotnetfiddle here: https://dotnetfiddle.net/rS0Am8

    using System;
    using System.Diagnostics;
    using System.Linq;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    
    public class Program
    {
        public void Main()
        {
            var json1 = @"[
            {""name"":""a1"", ""quantity"": 10 },
            {""name"":""a2"", ""quantity"": 11 },
            {""name"":""a3"", ""quantity"": 12 },
            {""name"":""a4"", ""quantity"": 13 },
            {""name"":""a5"", ""quantity"": 14 },
            ]";
            var json2 = @"[
            {""name"":""a1"", ""quantity"": 11 },
            {""name"":""b1"", ""quantity"": 1 },
            {""name"":""b2"", ""quantity"": 12 },
            {""name"":""a3"", ""quantity"": 13 },
            {""name"":""a5"", ""quantity"": 14 },
            ]";
            var retries = 10000;
    
            var sw = new Stopwatch();
    
            sw.Start();
    
            string result = "";
    
            for (var i = 0; i < retries; i++)
            {
                var json1Array = JArray.Parse(json1);
                var json2Array = JArray.Parse(json2);
                var json3Array = new JArray();
                foreach (var item in json1Array)
                {
                    var name = (string) item["name"];
                    var quantity = (int) item["quantity"];
                    var differ = quantity;
                    var itemJson2 = json2Array.Where(it => (string) it["name"] == name).FirstOrDefault();
                    if (itemJson2 != null)
                    {
                        differ = quantity - (int) itemJson2["quantity"];
                    }
                    json3Array.Add(new JObject() { { "name", name }, { "quantity", quantity }, { "differ", differ } });
                }
                result = JsonConvert.SerializeObject(json3Array);
            }
    
            sw.Stop();
            Console.WriteLine("Variant 1 (" + sw.ElapsedMilliseconds + "ms)");
            Console.WriteLine(result);
    
            sw.Restart();
            for (var i = 0; i < retries; i++)
            {
                var d1 = JsonConvert.DeserializeObject<JsonData[]>(json1).ToDictionary(d => d.Name, d => d.Quantity);
                var d2 = JsonConvert.DeserializeObject<JsonData[]>(json2).ToDictionary(d => d.Name, d => d.Quantity);
    
                result = JsonConvert.SerializeObject(d1.Select(kvp => new JsonResultData
                {
                    Name = kvp.Key,
                    Quantity = kvp.Value,
                    Differ = d2.ContainsKey(kvp.Key) ? kvp.Value - d2[kvp.Key] : kvp.Value
                }));
            }
    
            sw.Stop();
            Console.WriteLine("Variant 2 (" + sw.ElapsedMilliseconds + "ms)");
            Console.WriteLine(result);
        }
    
        // Define other methods and classes here
        class JsonData
        {
            [JsonProperty("name", Order = 1)]
            public string Name { get; set; }
    
            [JsonProperty("quantity", Order = 2)]
            public int Quantity { get; set; }
        }
    
        class JsonResultData : JsonData
        {
            [JsonProperty("differ", Order = 3)]
            public int Differ { get; set; }
        }
    }
    

    In DotNetFiddle, it seems like your method is quicker (approximately factor 5 to 10). However, running it locally in LINQPad, variant 2 runs up to twice as fast.

    I assume, it will depend a lot on your input, and how many iterations you will have. But, as Jon Skeet suggested: It is always best to actually try it out. That's what tools like LINQPad were developed for :-)

    UPDATE:

    Also, not using JsonConvert.SerializeObject(...) to generate the resulting JSON, but instead just creating it manually will also boost your performance. However, I would not recommend this, unless your Json structure remains as simple as you have described. In my tests, this saved about 30%. Example implementation:

    sw.Restart();
    for (var i = 0; i < retries; i++)
    {
        var d1 = JsonConvert.DeserializeObject<JsonData[]>(json1).ToDictionary(d => d.Name, d => d.Quantity);
        var d2 = JsonConvert.DeserializeObject<JsonData[]>(json2).ToDictionary(d => d.Name, d => d.Quantity);
    
        var sb = new StringBuilder();
        sb.Append("[");
        foreach (var kvp in d1)
        {
            sb.AppendFormat("{{\"name\":\"{0}\",\"quantity\":{1},\"differ\":{2}}}",
                kvp.Key.Replace("\"", "\\\""),
                kvp.Value.ToString(CultureInfo.InvariantCulture),
                (d2.ContainsKey(kvp.Key) ? kvp.Value - d2[kvp.Key] : kvp.Value).ToString(CultureInfo.InvariantCulture));
        }
        sb.Append("]");
        result = sb.ToString();
    }
    
    sw.Stop();
    Console.WriteLine("Variant 3 (" + sw.ElapsedMilliseconds + "ms)");
    Console.WriteLine(result);