0

Hello i have a project where i have a Json data deserialized to a object class then the object class is converter to a treeview, the tree view nodes are edited and i need to transform the treeview to json.

Object Class

public class CategoryLink
    {
        public string position { get; set; }
        public string category_id { get; set; }
    }

    public class StockItem
    {
        public string item_id { get; set; }
        public string product_id { get; set; }
        public string stock_id { get; set; }
        public string qty { get; set; }
        public bool is_in_stock { get; set; }
        public bool is_qty_decimal { get; set; }
        public bool show_default_notification_message { get; set; }
        public bool use_config_min_qty { get; set; }
        public string min_qty { get; set; }
        public string use_config_min_sale_qty { get; set; }
        public string min_sale_qty { get; set; }
        public bool use_config_max_sale_qty { get; set; }
        public string max_sale_qty { get; set; }
        public bool use_config_backorders { get; set; }
        public string backorders { get; set; }
        public bool use_config_notify_stock_qty { get; set; }
        public string notify_stock_qty { get; set; }
        public bool use_config_qty_increments { get; set; }
        public string qty_increments { get; set; }
        public bool use_config_enable_qty_inc { get; set; }
        public bool enable_qty_increments { get; set; }
        public bool use_config_manage_stock { get; set; }
        public bool manage_stock { get; set; }
        public object low_stock_date { get; set; }
        public bool is_decimal_divided { get; set; }
        public string stock_status_changed_auto { get; set; }
    }

    public class ExtensionAttributes
    {
        public IList<string> website_ids { get; set; }
        public IList<CategoryLink> category_links { get; set; }
        public StockItem stock_item { get; set; }
    }

    public class ProductLink
    {
        public string sku { get; set; }
        public string link_type { get; set; }
        public string linked_product_sku { get; set; }
        public string linked_product_type { get; set; }
        public string position { get; set; }
    }

    public class MediaGalleryEntry
    {
        public string id { get; set; }
        public string media_type { get; set; }
        public string label { get; set; }
        public string position { get; set; }
        public bool disabled { get; set; }
        public IList<string> types { get; set; }
        public string file { get; set; }
    }

    public class CustomAttribute
    {
        public string attribute_code { get; set; }
        public object value { get; set; }
    }

    public class Product
    {
        public string id { get; set; }
        public string sku { get; set; }
        public string name { get; set; }
        public string attribute_set_id { get; set; }
        public string price { get; set; }
        public string status { get; set; }
        public string visibility { get; set; }
        public string type_id { get; set; }
        public string created_at { get; set; }
        public string updated_at { get; set; }
        public string weight { get; set; }
        public ExtensionAttributes extension_attributes { get; set; }
        public IList<ProductLink> product_links { get; set; }
        public IList<object> options { get; set; }
        public IList<MediaGalleryEntry> media_gallery_entries { get; set; }
        public IList<object> tier_prices { get; set; }
        public IList<CustomAttribute> custom_attributes { get; set; }
    }

    public class ProductCreateUpdate
    {
        public Product product { get; set; }
        public bool saveOptions { get; set; }
    }

    //GET ALLL PRODUCTS

    public class Item
    {
        public string id { get; set; }
        public string sku { get; set; }
        public string name { get; set; }
        public string attribute_set_id { get; set; }
        public string price { get; set; }
        public string status { get; set; }
        public string visibility { get; set; }
        public string type_id { get; set; }
        public string created_at { get; set; }
        public string updated_at { get; set; }
        public string weight { get; set; }
        public ExtensionAttributes extension_attributes { get; set; }
        public IList<ProductLink> product_links { get; set; }
        public IList<object> options { get; set; }
        public IList<MediaGalleryEntry> media_gallery_entries { get; set; }
        public IList<object> tier_prices { get; set; }
        public IList<CustomAttribute> custom_attributes { get; set; }
    }

    public class SearchCriteria
    {
        public IList<object> filter_groups { get; set; }
        public string page_size { get; set; }
    }

    public class ProductGetAll
    {
        public IList<Item> items { get; set; }
        public SearchCriteria search_criteria { get; set; }
        public string total_count { get; set; }
    }

Class Deserialized object to Treeview

public static class ObjectToTreeView
    {
        private sealed class IndexContainer
        {
            private int _n;
            public int Inc() => _n++;
        }

        private static void FillTreeView(TreeNode node, JToken tok, Stack<IndexContainer> s)
        {
            if (tok.Type == JTokenType.Object)
            {
                TreeNode n = node;
                if (tok.Parent != null)
                {
                    if (tok.Parent.Type == JTokenType.Property)
                    {
                        n = node.Nodes.Add($"{((JProperty)tok.Parent).Name} <{tok.Type.ToString()}>");
                    }
                    else
                    {
                        n = node.Nodes.Add($"[{s.Peek().Inc()}] <{tok.Type.ToString()}>");
                    }
                }
                s.Push(new IndexContainer());
                foreach (var p in tok.Children<JProperty>())
                {
                    FillTreeView(n, p.Value, s);
                }
                s.Pop();
            }
            else if (tok.Type == JTokenType.Array)
            {
                TreeNode n = node;
                if (tok.Parent != null)
                {
                    if (tok.Parent.Type == JTokenType.Property)
                    {
                        n = node.Nodes.Add($"{((JProperty)tok.Parent).Name} <{tok.Type.ToString()}>");
                    }
                    else
                    {
                        n = node.Nodes.Add($"[{s.Peek().Inc()}] <{tok.Type.ToString()}>");
                    }
                }
                s.Push(new IndexContainer());
                foreach (var p in tok)
                {
                    FillTreeView(n, p, s);
                }
                s.Pop();
            }
            else
            {
                var name = string.Empty;
                var value = JsonConvert.SerializeObject(((JValue)tok).Value);

                if (tok.Parent.Type == JTokenType.Property)
                {
                    name = $"{((JProperty)tok.Parent).Name} : {value}";
                }
                else
                {
                    name = $"[{s.Peek().Inc()}] : {value}";
                }

                node.Nodes.Add(name);
            }
        }

        public static void SetObjectAsJson<T>(this TreeView tv, T obj)
        {
            tv.BeginUpdate();
            try
            {
                tv.Nodes.Clear();

                var s = new Stack<IndexContainer>();
                s.Push(new IndexContainer());
                FillTreeView(tv.Nodes.Add("ROOT"), JsonConvert.DeserializeObject<JToken>(JsonConvert.SerializeObject(obj)), s);
                s.Pop();
            }
            finally
            {
                tv.EndUpdate();
            }
        }
    }

I edit the node of the treeview and then i need to passes to json again to update the data, here a screenshot enter image description here

  • 2
    I suggest using `Newtonsoft.Json` library instead – Muaath Sep 21 '20 at 14:39
  • With JSON.NET you can deserialize directly to objects then bind those objects to the treeview. There's no need to work with the individual JToken instances. The reverse is just as easy - since you already have the objects, just call `JsonConvert.SerializeObject` on the root' – Panagiotis Kanavos Sep 21 '20 at 14:43
  • I am using Newtonsoft.Json already with this line string a = JsonConvert.SerializeObject(productEditTreeView.Nodes, Formatting.Indented); but only retries the root node, i need the treeview an not he object because i deit the treeview and i filled it with the object – Edgar Gomez Sep 21 '20 at 14:58
  • hey -- that code looks familiar :) https://stackoverflow.com/a/52920900/1204153 – Andy Sep 21 '20 at 15:02
  • Yes i tooked from the same post – Edgar Gomez Sep 21 '20 at 15:03
  • Yeah it's my code which is why I brought it up... I see what you are asking about. Anyway, you will probably have to bind the property value to each node. That way when you serialize you know the original type it was. – Andy Sep 21 '20 at 15:04
  • sorry if i not mention it in the main post, but i took the Andy's code, honestly what do you mean to bind the property value to each node? – Edgar Gomez Sep 21 '20 at 15:06
  • I wasn't saying it was a bad thing -- I just thought it was neat that people are actually using it. I'll try to put something together to show you. – Andy Sep 21 '20 at 15:07
  • OK -- answer added/updated to handle null values. – Andy Sep 21 '20 at 17:50

2 Answers2

0

Here is a new TreeView component that will allow you to change the JValue part of the object.

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Windows.Forms;

public class JsonTreeView : TreeView
{
    private JToken _object;

    private event EventHandler OnJsonObjectChanged;

    private static class ObjectToTreeView
    {
        public sealed class IndexContainer
        {
            private int _n;
            public int Inc() => _n++;
        }

        public static void FillTreeView(TreeNode node, JToken tok, Stack<IndexContainer> s)
        {
            if (tok.Type == JTokenType.Object)
            {
                TreeNode n = node;
                if (tok.Parent != null)
                {
                    if (tok.Parent.Type == JTokenType.Property)
                    {
                        n = node.Nodes.Add($"{((JProperty)tok.Parent).Name} <{tok.Type}>");
                    }
                    else
                    {
                        n = node.Nodes.Add($"[{s.Peek().Inc()}] <{tok.Type}>");
                    }
                }
                s.Push(new IndexContainer());
                foreach (var p in tok.Children<JProperty>())
                {
                    FillTreeView(n, p.Value, s);
                }
                s.Pop();
            }
            else if (tok.Type == JTokenType.Array)
            {
                TreeNode n = node;
                if (tok.Parent != null)
                {
                    if (tok.Parent.Type == JTokenType.Property)
                    {
                        n = node.Nodes.Add($"{((JProperty)tok.Parent).Name} <{tok.Type}>");
                    }
                    else
                    {
                        n = node.Nodes.Add($"[{s.Peek().Inc()}] <{tok.Type}>");
                    }
                }
                s.Push(new IndexContainer());
                foreach (var p in tok)
                {
                    FillTreeView(n, p, s);
                }
                s.Pop();
            }
            else
            {
                var value = JsonConvert.SerializeObject(((JValue)tok).Value);

                string name;
                if (tok.Parent.Type == JTokenType.Property)
                {
                    name = $"{((JProperty)tok.Parent).Name} : {value}";
                }
                else
                {
                    name = $"[{s.Peek().Inc()}] : {value}";
                }
                // NEW: store a reference to this token
                node.Nodes.Add(name).Tag = tok;
            }
        }
    }

    public JsonTreeView()
    {
        LabelEdit = true;
        BeforeLabelEdit += OnBeforeLabelEdit;
        AfterLabelEdit += OnAfterLabelEdit;
    }

    private void OnBeforeLabelEdit(object sender, NodeLabelEditEventArgs e)
    {
        e.CancelEdit = e.Node.Tag == null;
    }

    private void OnAfterLabelEdit(object sender, NodeLabelEditEventArgs e)
    {
        if (string.IsNullOrEmpty(e.Label)) { return; }

        var originalToken = (JToken)e.Node.Tag;
        var newText = e.Label;

        // must have separator
        var indSplit = newText.IndexOf(" :");
        if (indSplit == -1)
        {
            e.CancelEdit = true;
            return;
        }

        // front must match original name
        if (!newText.StartsWith(e.Node.Text.Substring(0, indSplit + 2)))
        {
            e.CancelEdit = true;
            return;
        }

        // new text should have value
        var newTextValue = newText.Substring(indSplit + 2).Trim();
        if (string.IsNullOrWhiteSpace(newTextValue))
        {
            e.CancelEdit = true;
            return;
        }

        // let newtonsoft create a new value based on user entry
        JValue newValue;
        try
        {
            newValue = JsonConvert.DeserializeObject<JValue>(newTextValue);
        }
        catch (JsonReaderException)
        {
            e.CancelEdit = true;
            return;
        }

        // types must match (unless it's null)
        {
            var origType = ((JValue)originalToken).Type;
            if ((origType == JTokenType.Null) || (newValue.Type == JTokenType.Null))
            {
                // we are going to skip this conversion because there is no type
            }
            else
            {
                if(origType != newValue.Type)
                {
                    e.CancelEdit = true;
                    return;
                }
            }
        }

        // set it
        ((JValue)originalToken).Value = newValue?.Value;

        // reset the value incase the value text changed
        e.CancelEdit = true;
        e.Node.Text = $"{e.Node.Text.Substring(0, indSplit + 3)}" +
            $"{JsonConvert.SerializeObject(((JValue)originalToken).Value)}";

        // fire off notification
        OnJsonObjectChanged?.Invoke(this, EventArgs.Empty);
    }

    public void SetJsonObject(object obj)
    {
        BeginUpdate();
        try
        {
            Nodes.Clear();

            var s = new Stack<ObjectToTreeView.IndexContainer>();
            s.Push(new ObjectToTreeView.IndexContainer());
            _object = JsonConvert.DeserializeObject<JToken>(JsonConvert.SerializeObject(obj));
            ObjectToTreeView.FillTreeView(Nodes.Add("ROOT"), _object, s);
            s.Pop();
        }
        finally
        {
            EndUpdate();
        }
    }

    public TResult GetJsonObject<TResult>()
    {
        return JsonConvert.DeserializeObject<TResult>(JsonConvert.SerializeObject(_object));
    }
}
Andy
  • 12,859
  • 5
  • 41
  • 56
  • please see my new post, thnaks a lot andy – Edgar Gomez Sep 21 '20 at 19:01
  • @EdgarGomez -- if this answer helped you, would you mind accepting it by clicking the checkbox to the upper-left of the answer? – Andy Sep 21 '20 at 19:46
  • Just a question i put beakpoints in the events BeforeLabelEdit += OnBeforeLabelEdit; and AfterLabelEdit += OnAfterLabelEdit; but the code never reached that point even after edit the porpertie Text of the node. – Edgar Gomez Sep 21 '20 at 20:00
  • @EdgarGomez -- then you aren't instantiating it right. That's the constructor. If it's not hitting the constructor, then you aren't creating it right. – Andy Sep 21 '20 at 20:22
  • It works but dor edditing you need to bouble click the line and edit, by code doesn't fire the event, marked as solution – Edgar Gomez Sep 22 '20 at 11:56
  • @EdgarGomez -- You can add it to your form. Check out this answer: https://stackoverflow.com/a/63441486/1204153 and scroll to the section "How To Use This". Basically you edit the designer file to point at the new class. – Andy Sep 22 '20 at 14:06
0

Here is the code of my main form, is pretty short, using the code of @Andy i create a instance of JsonTreeView. I set the json treview using jsonEditProduct.SetJsonObject(item); and return back the json object using Item item = jsonEditProduct.GetJsonObject();, but when i edit the json text properties using this block of code

if (jsonEditProduct.SelectedNode.Text.Contains(":"))
                {
                    string[] nameField = jsonEditProduct.SelectedNode.Text.Split(new[] { ':' }, 2);
                    jsonEditProduct.SelectedNode.Text = nameField[0] + ":" + " \"" + fieldJsonTextBox.Text + "\"";
                }

And get the object json back after, the property doesn't change in the object retrieve.

Here is my complete code of the main form

using Magento;
using Magento.Products;
using System;
using System.Drawing;
using System.Windows.Forms;

namespace MagentoAPI
{
    public partial class MainForm : Form
    {
        private WaitForm _waitForm;
        private MagentoMain magento;
        private JsonTreeView jsonEditProduct = new JsonTreeView();
        ProductGetAll productsGetAll;

        public MainForm(MagentoMain magento)
        {
            InitializeComponent();
            LoadIconsTabControl();
            panel11.Controls.Add(jsonEditProduct);
            jsonEditProduct.Dock = DockStyle.Fill;
            jsonEditProduct.AfterSelect += new TreeViewEventHandler(jsonEditProduct_AfterSelect);
            this.magento = magento;
        }

        internal void LoadIconsTabControl()
        {
            ImageList iconsList = new ImageList();
            iconsList.TransparentColor = Color.Orange;
            iconsList.ColorDepth = ColorDepth.Depth32Bit;
            iconsList.ImageSize = new Size(48, 48);
            var icon = Properties.Resources.productEdit;
            iconsList.Images.Add(icon);
            icon = Properties.Resources.productAdd;
            iconsList.Images.Add(icon);
            tabControl.ImageList = iconsList;
            tabControl.TabPages[0].ImageIndex = 0;
            tabControl.TabPages[1].ImageIndex = 1;
        }

        protected void ShowWaitForm(string message)
        {
            // don't display more than one wait form at a time
            if (_waitForm != null && !_waitForm.IsDisposed)
            {
                return;
            }

            _waitForm = new WaitForm();
            _waitForm.SetMessage(message); // "Loading data. Please wait..."
            _waitForm.TopMost = true;
            _waitForm.StartPosition = FormStartPosition.CenterScreen;
            _waitForm.Show();
            _waitForm.Refresh();

            // force the wait window to display for at least 700ms so it doesn't just flash on the screen
            System.Threading.Thread.Sleep(700);
            Application.Idle += OnLoaded;
        }

        private void OnLoaded(object sender, EventArgs e)
        {
            Application.Idle -= OnLoaded;
            _waitForm.Close();
        }

        private void ProductsGetAllButton_Click(object sender, EventArgs e)
        {
            ShowWaitForm("Espere descargando productos...");
            productsGetAll = magento.GetAllProdcuts();
            skuListBox.Items.Clear();
            foreach (Item item in productsGetAll.items)
            {
                skuListBox.Items.Add(item.sku);
            }
            
        }

        private void skuListBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            foreach (Item item in productsGetAll.items)
            {
                if (item.sku == skuListBox.SelectedItem.ToString())
                {
                    jsonEditProduct.SetJsonObject(item);
                }
            }
        }

        private void jsonEditProduct_AfterSelect(object sender, TreeViewEventArgs e)
        {
            if (jsonEditProduct.SelectedNode.Text.Contains(":"))
            {
                string[] nameField = jsonEditProduct.SelectedNode.Text.Split(new[] { ':' }, 2);
                nameJsonTextBox.Text = nameField[0].Trim(' ').Trim('"');
                fieldJsonTextBox.Text = nameField[1].Trim(' ').Trim('"');
            }
        }

        private void fieldJsonTextBox_TextChanged(object sender, EventArgs e)
        {
            if (jsonEditProduct.SelectedNode.Text.Contains(":"))
            {
                string[] nameField = jsonEditProduct.SelectedNode.Text.Split(new[] { ':' }, 2);
                jsonEditProduct.SelectedNode.Text = nameField[0] + ":" + " \"" + fieldJsonTextBox.Text + "\"";
            }
        }

        private void updateProductButton_Click(object sender, EventArgs e)
        {
            Item item = jsonEditProduct.GetJsonObject<Item>();
            //UPDATE NOT READY
        }
    }
}