You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中如何通过JSON配置动态修改动态对象的指定属性

解决方案:基于路径字符串修改C#动态JSON对象

核心思路

我们需要解析配置中的路径字符串(支持属性访问.key和数组索引[0]),遍历目标JSON对象找到对应节点后完成赋值。同时通过简单的字符串处理取消jsonObj前缀限制。

推荐使用Newtonsoft.Json的JObject类型操作(默认反序列化dynamic时实际就是JObject),相比ExpandoObject更稳定且原生支持JSON结构操作。


实现代码(JObject方案)

1. 工具方法封装

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

public static class JsonPathSetter
{
    /// <summary>
    /// 根据路径字符串设置JObject的值
    /// </summary>
    /// <param name="root">目标JSON根对象</param>
    /// <param name="path">配置中的路径字符串</param>
    /// <param name="value">要设置的值</param>
    /// <param name="createIfMissing">路径不存在时是否自动创建节点</param>
    public static void SetValueByPath(JObject root, string path, object value, bool createIfMissing = true)
    {
        // 移除可选的jsonObj前缀
        if (path.StartsWith("jsonObj."))
        {
            path = path.Substring("jsonObj.".Length);
        }
        else if (path.Equals("jsonObj", StringComparison.OrdinalIgnoreCase))
        {
            // 直接替换整个根对象
            root.ReplaceAll(JToken.FromObject(value));
            return;
        }

        var pathSegments = path.Split('.');
        JToken current = root;

        // 遍历到目标节点的父节点
        for (int i = 0; i < pathSegments.Length - 1; i++)
        {
            current = GetNextToken(current, pathSegments[i], createIfMissing);
            if (current == null)
            {
                throw new ArgumentException($"路径不存在:{path}");
            }
        }

        // 设置最后一个节点的值
        SetLastTokenValue(current, pathSegments[^1], value);
    }

    private static JToken GetNextToken(JToken current, string segment, bool createIfMissing)
    {
        // 处理数组索引格式(如key[0]或[0])
        if (segment.Contains("["))
        {
            var parts = segment.Split(new[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries);
            return parts.Length switch
            {
                // 直接数组索引(如[0])
                1 => HandleArrayIndex(current, int.Parse(parts[0]), createIfMissing),
                // 属性+数组索引(如key[0])
                2 => HandlePropertyArray(current, parts[0], int.Parse(parts[1]), createIfMissing),
                _ => throw new ArgumentException($"无效的路径段:{segment}")
            };
        }
        // 处理普通属性
        else
        {
            var prop = current[segment];
            if (prop == null && createIfMissing)
            {
                prop = new JObject();
                ((JObject)current)[segment] = prop;
            }
            return prop;
        }
    }

    private static JToken HandleArrayIndex(JToken current, int index, bool createIfMissing)
    {
        if (current is not JArray array)
        {
            throw new InvalidOperationException($"当前节点不是数组,无法使用索引:[{index}]");
        }

        if (index >= array.Count)
        {
            if (!createIfMissing) return null;
            // 自动填充数组到指定索引
            while (array.Count <= index)
            {
                array.Add(new JObject());
            }
        }
        return array[index];
    }

    private static JToken HandlePropertyArray(JToken current, string propName, int index, bool createIfMissing)
    {
        if (current is not JObject obj)
        {
            throw new InvalidOperationException($"当前节点不是对象,无法访问属性:{propName}");
        }

        var prop = obj[propName];
        if (prop == null)
        {
            if (!createIfMissing) return null;
            prop = new JArray();
            obj[propName] = prop;
        }

        if (prop is not JArray array)
        {
            throw new InvalidOperationException($"属性{propName}不是数组,无法使用索引:[{index}]");
        }

        if (index >= array.Count && createIfMissing)
        {
            while (array.Count <= index)
            {
                array.Add(new JObject());
            }
        }
        return array[index];
    }

    private static void SetLastTokenValue(JToken parent, string lastSegment, object value)
    {
        if (lastSegment.Contains("["))
        {
            var parts = lastSegment.Split(new[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length == 1)
            {
                // 设置数组元素值
                if (parent is JArray array)
                {
                    int index = int.Parse(parts[0]);
                    array[index] = JToken.FromObject(value);
                }
                else
                {
                    throw new InvalidOperationException($"父节点不是数组,无法设置索引:{lastSegment}");
                }
            }
            else if (parts.Length == 2)
            {
                // 设置数组内对象的属性值
                if (parent is JObject obj)
                {
                    var prop = obj[parts[0]] as JArray;
                    prop[int.Parse(parts[1])] = JToken.FromObject(value);
                }
                else
                {
                    throw new InvalidOperationException($"父节点不是对象,无法设置属性:{parts[0]}");
                }
            }
        }
        else
        {
            // 设置普通属性值
            if (parent is JObject obj)
            {
                obj[lastSegment] = JToken.FromObject(value);
            }
            else
            {
                throw new InvalidOperationException($"父节点不是对象,无法设置属性:{lastSegment}");
            }
        }
    }
}

2. 使用示例

// 待修改的目标JSON
string targetJson = @"
{
  ""someOuterKey"": {
    ""someInnerKey"": {
      ""someEvenMoreInnerKey"": ""someCurrentValue""
    }
  },
  ""arrayKey"": [{""name"": ""oldName""}, {""name"": ""oldName2""}]
}";

// 配置文件JSON(支持带/不带jsonObj前缀)
string configJson = @"
{
  ""someOuterKey.someInnerKey.someEvenMoreInnerKey"": ""newValue"",
  ""jsonObj.arrayKey[1].name"": ""newName2"",
  ""jsonObj.newOuterKey.newInnerKey"": ""newAddedValue""
}";

// 反序列化目标对象和配置
JObject targetObj = JsonConvert.DeserializeObject<JObject>(targetJson);
Dictionary<string, object> config = JsonConvert.DeserializeObject<Dictionary<string, object>>(configJson);

// 遍历配置完成赋值
foreach (var kvp in config)
{
    JsonPathSetter.SetValueByPath(targetObj, kvp.Key, kvp.Value);
}

// 输出修改后的JSON
string updatedJson = targetObj.ToString(Formatting.Indented);
Console.WriteLine(updatedJson);

关键特性说明

  1. 前缀兼容:自动识别并移除jsonObj.前缀,用户可以自由选择是否添加
  2. 路径解析:支持普通属性(.key)、数组索引([0])、属性+数组索引(key[0])三种格式
  3. 自动创建节点:开启createIfMissing参数后,路径不存在时会自动创建对应的JObject/JArray
  4. 类型安全:针对不同节点类型(对象/数组)做了校验,避免运行时错误

内容的提问来源于stack exchange,提问作者justanotherguy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 00:27:08