Search code examples
c#jsonwinformspropertygriddynamicobject

How does the PropertyGrid control display two levels of nested dynamic JSON objects?


I have a requirement that several of my colleagues' configuration files should be displayed uniformly with the PropertyGrid control, which I have implemented with reference to the following post:https://www.codeproject.com/Articles/193462/Using-PropertyGrid-to-Display-and-Edit-Dynamic-Obj.

My way is: define a ConfigObject object first, then deserialized json configuration file into ConfigObject object using JsonConvert.Convert(Newtonsoft.Json), and then assigned to the PropertyGrid.SelectedObject. But this way I can only display and edit the one level of nested json objects, if more than two levels of nested structure, so the nested property field will be not editable.

Consider the following two-level JSON structure:

{
  "DAMultiCast": "18:80:c2:00:00:0e",
  "SA": "18:60:24:A8:77:FF",
  "gPTPType": "0x88f7",
  "AVTPType": "0x22f0",
  "Initial": {
    "SyncMessageType": "0x10",
    "FollowupMessageType": "0x18",
    "ReqMessageType": "0x12",
    "RespMessageType": "0x13",
    "RespFollowupMessageType": "0x1A",
    "versionPTP": "0x02",
    "SyncMessagelength": "44",
    "FollowupMessagelength": "76",
    "ReqMessagelength": "54",
    "subdomainnumber": "0",
    "resv0": "0x00",
    "Syncflagfield": "0x0208",
    "Followupflagfield": "0x0008",
    "correctionField": "00:00:00:00:00:00:00:00",
    "resv1": "00:00:00:00",
    "SyncClockIdentity": "01:02:03:ff:fe:46:76:34",
    "RespClockIdentity": "00:22:97:ff:fe:80:0d:f2",
    "sourcePortId": "0x0001",
    "sequenceId": "143",
    "SyncControlField": "0",
    "FollowupControlField": "2",
    "DelayReqControlField": "5",
    "logMessagePeriod": "-3",
    "tlvType": "3",
    "lengthField": "28",
    "organizationId": "32962",
    "organizationSubType": "1",
    "cumulativeScaledRateOffset": "0",
    "gmTimeBaseIndicator": "0",
    "lastGmPhaseChange": "00:00:00:00:00:00:00:00:00:00:00:00",
    "scaledLastGmFreqChange": "0",
    "requestingPortIdentity": "01:02:03:ff:fe:46:76:34",
    "requestingPortId": "1"
  },
  "TM1_TG1_6.1.1B": {
    "WaitTime1": "10",
    "WaitTime2": "2"
  }
}

Solution

  • This might give you a hint to achieve your goal.

    Source1: Dynamically Create a Class at Runtime
    Source2: PropertyGrid Browsable not found for entity framework created property, how to find it?
    Source3: Make a Property Read-Only in PropertyGrid

    OUTPUT:
    enter image description here
    CODE:

    private void loadJsonToPropertyGrid(string jsonString)
        {
            var jsonObject = JsonConvert.DeserializeObject<JObject>(jsonString);
            var obj = createClass("Item", jsonObject);
            var customClass = JsonConvert.DeserializeObject(jsonString, obj.GetType());
            var customClassType = customClass.GetType();
    
            DynamicTypeDescriptor typeDescriptor = new DynamicTypeDescriptor(customClassType);
    
            var propertyDescriptorList = typeDescriptor.Properties.Cast<PropertyDescriptor>().ToList()
            .Where(p => p.PropertyType.Name != "String").ToList();
    
            propExpandAndReadOnly(propertyDescriptorList);
    
            propertyGrid1.SelectedObject = typeDescriptor.FromComponent(customClass);
        }
    
        private void propExpandAndReadOnly(List<PropertyDescriptor> propertyDescriptorList)
        {
            foreach (var propertyDescriptor in propertyDescriptorList)
            {
                propertyDescriptor.SetReadOnlyAttribute(true);
                propertyDescriptor.SetExpandableAttribute(true);
    
                DynamicTypeDescriptor typeDescriptor = new DynamicTypeDescriptor(propertyDescriptor.PropertyType);
                var chilPropertyDescriptorList = typeDescriptor.Properties.Cast<PropertyDescriptor>().ToList()
                .Where(p => p.PropertyType.Name != "String").ToList();
                propExpandAndReadOnly(chilPropertyDescriptorList);
            }
        }
    
        private Type[] getPropertiesType(string[] properties, JObject jsonObject)
        {
            var propertyTypes = new List<Type>();
    
            foreach (var property in properties)
            {
                var jToken = jsonObject.GetValue(property);
                Type propertyType;
    
                if (jToken.HasValues)
                {
                    var obj = createClass(property, (JObject)jsonObject.GetValue(property));
                    propertyType = obj.GetType();
                }
                else
                {
                    propertyType = typeof(string);
                }
    
                propertyTypes.Add(propertyType);
            }
    
            return propertyTypes.ToArray();
        }
    
        private object createClass(string name, JObject jsonObject)
        {
            MyClassBuilder MCB = new MyClassBuilder(name);
            var properties = jsonObject.Properties().Select(p => p.Name).ToArray();
            var propertiesType = getPropertiesType(properties, jsonObject);
            var obj = MCB.CreateObject(properties, propertiesType);
    
            return obj;
        }
    

    UPDATE
    Creation of PropertyDescriptorExtensions

    public static class PropertyDescriptorExtensions
    {
        public static void SetReadOnlyAttribute(this PropertyDescriptor p, bool value)
        {
            var attributes = p.Attributes.Cast<Attribute>()
                .Where(x => !(x is ReadOnlyAttribute)).ToList();
    
            attributes.Add(new ReadOnlyAttribute(value));
    
            typeof(MemberDescriptor).GetProperty("AttributeArray",
                BindingFlags.Instance | BindingFlags.NonPublic)
                .SetValue((MemberDescriptor)p, attributes.ToArray());
        }
    
        public static void SetExpandableAttribute(this PropertyDescriptor p, bool value)
        {
            var attributes = p.Attributes.Cast<Attribute>()
                .Where(x => !(x is ReadOnlyAttribute)).ToList();
    
            if (value)
            {
                attributes.Add(new TypeConverterAttribute(typeof(ExpandableObjectConverter)));
            }
    
            typeof(MemberDescriptor).GetProperty("AttributeArray",
                BindingFlags.Instance | BindingFlags.NonPublic)
                .SetValue((MemberDescriptor)p, attributes.ToArray());
        }
    }
    

    Happy coding, cheers!