Products Technologies Demo Docs Blog Support Company

Reliably Detect Property Changes in Objects Using Serialization

This article shows how to reliably detect property changes in objects such as images or barcodes using serialization. It demonstrates how to serialize objects to JSON and how to compare the serialized data to detect property changes.

Reliably Detect Property Changes in Objects Using Serialization

When the user visually changes a property such as the size or location of a TXTextControl.FrameBase object such as images or barcodes, events are fired to help developers detect these changes. When properties are changed in a dialog such as the TXTextControl.BarcodeLayoutDialog, the TX Text Control's Changed event is fired to indicate that changes have been made to the object. However, the exact change is not returned and must be determined by comparing the objects.

Serializing Objects

By serializing the object before and after the change, we can compare and find the differences in a very efficient way. The JsonSerializerHelper class implements the SerializeObjectToJson public method that uses the .NET Json implementation to serialize the object.

public static string SerializeObjectToJson<T>(T obj)
{
  JsonSerializerOptions options = new JsonSerializerOptions
  {
    WriteIndented = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
    Converters =
    {
      new IgnoreIntPtrConverter()
    }
  };

  try
  {
    string jsonString = JsonSerializer.Serialize(obj, options);
    return jsonString;
  }
  catch (Exception ex)
  {
    return string.Empty;
  }
}

The other methods compare the serialized objects by comparing the values of all keys at all levels. It is important to check the complete object hierarchy to get access to all properties. For example, the TXTextControl.BarcodeFrame class implements the inherited properties of FrameBase, and the actual properties of the barcode, such as text or color, are stored in the underlying TXTextControl.Barcode.TXBarcodeControl object.

Comparing Barcode Objects

When a barcode object is changed in the dialog, the Changed event is fired. The following code shows how to compare the barcode objects before and after the change:

var curBarcode = textControl1.Barcodes.GetItem();
var curBarcodeJson = JsonSerializerHelper.SerializeObjectToJson(curBarcode);

var dialogResult = textControl1.BarcodeLayoutDialog();

if (dialogResult == DialogResult.OK)
{
  var newBarcode = textControl1.Barcodes.GetItem();
  var newBarcodeJson = JsonSerializerHelper.SerializeObjectToJson(newBarcode);

  var differences = JsonSerializerHelper.CompareSerializedObjects(curBarcodeJson, newBarcodeJson);

  foreach (var difference in differences)
  {
    Debug.WriteLine(difference);
  }
}

First, we serialize the barcode object before opening the BarcodeLayoutDialog. After the dialog has been successfully closed, we have the same object and serialize it for comparison. For the following example, we will change the text of the QR code from "A" to "ABC":

Barcode object comparison

In this sample code, we print the differences, which are returned in a list of strings like this:

barcode.text: A != ABC
barcode.upperTextLength: 1 != 3

You can see that the property Barcode.Text and Barcode.UpperTextLength has been updated by the dialog.

JsonSerializerHelper Implementation

Here is the full implementation of the JsonSerializerHelper class:

using System.Text.Json;
using System.Text.Json.Serialization;

public class JsonSerializerHelper
{
  public static string SerializeObjectToJson<T>(T obj)
  {
    JsonSerializerOptions options = new JsonSerializerOptions
    {
      WriteIndented = true,
      PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
      DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
      Converters =
      {
        new IgnoreIntPtrConverter()
      }
    };

    try
    {
      string jsonString = JsonSerializer.Serialize(obj, options);
      return jsonString;
    }
    catch (Exception ex)
    {
      return string.Empty;
    }
  }

  private class IgnoreIntPtrConverter : JsonConverter<IntPtr>
  {
    public override IntPtr Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
      throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, IntPtr value, JsonSerializerOptions options)
    {
      writer.WriteNullValue();
    }
  }

  public static List<string> CompareSerializedObjects(string json1, string json2)
  {
    var differences = new List<string>();

    try
    {
      var dictionary1 = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json1);
      var dictionary2 = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json2);

      foreach (var key in dictionary1.Keys)
      {
        if (dictionary2.ContainsKey(key))
        {
          var value1 = dictionary1[key];
          var value2 = dictionary2[key];

          if (value1.ValueKind == JsonValueKind.Object && value2.ValueKind == JsonValueKind.Object)
          {
            var subDict1 = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(value1.GetRawText());
            var subDict2 = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(value2.GetRawText());

            CompareDictionaries(subDict1, subDict2, differences, key);
          }
        }
        else
        {
          differences.Add($"{key}: {dictionary1[key]} != null");
        }
      }

      foreach (var key in dictionary2.Keys)
      {
        if (!dictionary1.ContainsKey(key))
        {
          differences.Add($"{key}: null != {dictionary2[key]}");
        }
      }
    }
    catch (Exception ex)
    {
      // exception handling
    }

    return differences;
  }

  private static void CompareDictionaries(Dictionary<string, JsonElement> dict1, Dictionary<string, JsonElement> dict2, List<string> differences, string parentKey)
  {
    foreach (var key in dict1.Keys)
    {
      var fullKey = $"{parentKey}.{key}";

      if (!dict2.ContainsKey(key))
      {
        differences.Add($"{fullKey}: {dict1[key]} != null");
      }
      else if (!JsonElementEquals(dict1[key], dict2[key]))
      {
        differences.Add($"{fullKey}: {dict1[key]} != {dict2[key]}");
      }
    }

    foreach (var key in dict2.Keys)
    {
      if (!dict1.ContainsKey(key))
      {
        var fullKey = $"{parentKey}.{key}";
        differences.Add($"{fullKey}: null != {dict2[key]}");
      }
    }
  }

  private static bool JsonElementEquals(JsonElement element1, JsonElement element2)
  {
    if (element1.ValueKind != element2.ValueKind)
    {
      return false;
    }

    switch (element1.ValueKind)
    {
      case JsonValueKind.Object:
        var dict1 = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(element1.GetRawText());
        var dict2 = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(element2.GetRawText());
        return DictionariesEqual(dict1, dict2);

      case JsonValueKind.Array:
        if (element1.GetArrayLength() != element2.GetArrayLength())
        {
          return false;
        }

        var enumerator1 = element1.EnumerateArray();
        var enumerator2 = element2.EnumerateArray();

        while (enumerator1.MoveNext() && enumerator2.MoveNext())
        {
          if (!JsonElementEquals(enumerator1.Current, enumerator2.Current))
          {
            return false;
          }
        }
        return true;

      default:
        return element1.ToString() == element2.ToString();
    }
  }

  private static bool DictionariesEqual(Dictionary<string, JsonElement> dict1, Dictionary<string, JsonElement> dict2)
  {
    if (dict1.Count != dict2.Count)
    {
      return false;
    }

    foreach (var kvp in dict1)
    {
      if (!dict2.ContainsKey(kvp.Key) || !JsonElementEquals(kvp.Value, dict2[kvp.Key]))
      {
        return false;
      }
    }

    return true;
  }
}

Conclusion

By serializing objects before and after a change, you can easily compare the differences. This is a very efficient way to detect changes in complex objects such as barcode objects in TX Text Control.

Stay in the loop!

Subscribe to the newsletter to receive the latest updates.

Windows Forms

Text Control combines the power of a reporting tool and an easy-to-use WYSIWYG word processor - fully programmable and embeddable in your Windows Forms application. TX Text Control .NET for Windows Forms is a royalty-free, fully programmable rich edit control that offers developers a broad range of word processing features in a reusable component for Visual Studio.

See Windows Forms products

Related Posts

ASP.NETWindows FormsWPF

TX Text Control 33.0 SP3 is Now Available: What's New in the Latest Version

TX Text Control 33.0 Service Pack 3 is now available, offering important updates and bug fixes for all platforms. If you use TX Text Control in your document processing applications, this service…


ASP.NETWindows FormsWPF

TX Text Control 33.0 SP2 is Now Available: What's New in the Latest Version

TX Text Control 33.0 Service Pack 2 is now available, offering important updates and bug fixes for all platforms. If you use TX Text Control in your document processing applications, this service…


ASP.NETWindows FormsWPF

Document Lifecycle Optimization: Leveraging TX Text Control's Internal Format

Maintaining the integrity and functionality of documents throughout their lifecycle is paramount. TX Text Control provides a robust ecosystem that focuses on preserving documents in their internal…


ActiveXASP.NETWindows Forms

Expert Implementation Services for Legacy System Modernization

We are happy to officially announce our partnership with Quality Bytes, a specialized integration company with extensive experience in modernizing legacy systems with TX Text Control technologies.


ActiveXASP.NETWindows Forms

Service Pack Releases: What's New in TX Text Control 33.0 SP1 and 32.0 SP5

TX Text Control 33.0 Service Pack 1 and TX Text Control 32.0 Service Pack 5 have been released, providing important updates and bug fixes across platforms. These service packs improve the…