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

JSON序列化时如何将父对象Type和ID注入子Service的URI?

解决JSON序列化时子对象获取父对象属性生成URI的问题

这个问题我之前也碰到过——JSON.NET的IValueProvider确实是在模型契约层面工作的,没法直接拿到父对象的运行时值。不过有两个比较优雅的解决思路,我给你详细拆解:

方案一:自定义JsonConverter处理父对象(推荐)

这个方法直接在序列化Person对象时,主动处理子集合Services,把父对象的Type和ID注入到ServiceUri的占位符中,逻辑清晰且不依赖上下文。

步骤1:实现Person的自定义转换器

public class PersonConverter : JsonConverter<Person>
{
    public override void WriteJson(JsonWriter writer, Person person, JsonSerializer serializer)
    {
        // 先把Person序列化为JObject,保留原有序列化逻辑
        var personObj = JObject.FromObject(person, serializer);

        // 处理Services集合
        if (person.Services != null)
        {
            var servicesArray = new JArray();
            foreach (var service in person.Services)
            {
                // 序列化单个Service
                var serviceObj = JObject.FromObject(service, serializer);
                // 替换占位符生成真实URI
                var resolvedUri = service.ServiceUri
                    .Replace("{type}", person.Type)
                    .Replace("{id}", person.ID.ToString());
                // 更新ServiceUri字段
                serviceObj["serviceUri"] = resolvedUri;
                servicesArray.Add(serviceObj);
            }
            // 替换原有的services节点
            personObj["services"] = servicesArray;
        }

        // 最终写入JSON
        personObj.WriteTo(writer);
    }

    public override Person ReadJson(JsonReader reader, Type objectType, Person existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        // 如果不需要自定义反序列化,直接用默认逻辑即可
        return serializer.Deserialize<Person>(reader);
    }
}

步骤2:注册转换器并序列化

在序列化Person时,把自定义转换器添加到JsonSerializerSettings中:

var person = new Person
{
    ID = Guid.Parse("ae330dc8-0217-4c58-b64b-44a1c7ba4f46"),
    Type = "Person",
    Name = "Fred",
    Services = new List<Service>
    {
        new Service { ServiceName = "ListNotes", ServiceUri = "https://server/api/notes/{type}/{id}" },
        new Service { ServiceName = "ListContactDetails", ServiceUri = "https://server/api/contacts/{type}/{id}" }
    }
};

var settings = new JsonSerializerSettings();
settings.Converters.Add(new PersonConverter());
// 格式化输出方便查看
settings.Formatting = Formatting.Indented;

var json = JsonConvert.SerializeObject(person, settings);

这个方案的优势是:

  • 完全保留你原有的IValueProvider逻辑(比如ObjectUri的处理)
  • 不依赖全局上下文,避免嵌套序列化时的冲突
  • 代码逻辑直观,后期维护成本低

方案二:利用JsonSerializer上下文传递父对象信息

如果你不想修改序列化流程,也可以通过JSON.NET的StreamingContext传递父对象的运行时值,让ServiceUriValueProvider能读取到这些信息。

步骤1:修改ServiceUriValueProvider

让它从序列化上下文中获取父对象的Type和ID:

public class ServiceUriValueProvider : IValueProvider
{
    private readonly PropertyInfo _targetProperty;

    public ServiceUriValueProvider(PropertyInfo targetProperty)
    {
        _targetProperty = targetProperty;
    }

    public void SetValue(object target, object value)
    {
        _targetProperty.SetValue(target, value);
    }

    public object GetValue(object target)
    {
        var service = target as Service;
        if (service == null) return null;

        // 从当前序列化上下文获取父对象信息
        var currentSerializer = JsonSerializer.Current;
        if (currentSerializer?.Context.Items != null)
        {
            if (currentSerializer.Context.Items.TryGetValue("ParentType", out var typeObj) &&
                currentSerializer.Context.Items.TryGetValue("ParentId", out var idObj))
            {
                var type = typeObj as string;
                var id = idObj as Guid?;
                if (!string.IsNullOrEmpty(type) && id.HasValue)
                {
                    var template = _targetProperty.GetValue(target) as string;
                    return template?.Replace("{type}", type).Replace("{id}", id.Value.ToString());
                }
            }
        }

        // 没有上下文时返回原模板
        return _targetProperty.GetValue(target);
    }
}

步骤2:序列化时注入上下文

在序列化Person前,把父对象的Type和ID存入StreamingContext:

var settings = new JsonSerializerSettings
{
    ContractResolver = new CustomContractResolver(), // 你的自定义契约解析器,已注册ServiceUriValueProvider
    Formatting = Formatting.Indented
};

var serializer = JsonSerializer.Create(settings);
// 注入父对象信息到上下文
serializer.Context = new StreamingContext(StreamingContextStates.Other, new Dictionary<string, object>
{
    ["ParentType"] = person.Type,
    ["ParentId"] = person.ID
});

using (var sw = new StringWriter())
using (var writer = new JsonTextWriter(sw))
{
    serializer.Serialize(writer, person);
    var json = sw.ToString();
}

这个方案适合需要保留IValueProvider统一逻辑的场景,但要注意JsonSerializer.Current仅在Json.NET 6.0+和.NET Framework 4.5+支持,且多层嵌套序列化时上下文可能被覆盖。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:22:22