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

如何在C#中为ClickHouse Kafka引擎正确序列化Nested列?

如何在C#中为ClickHouse Kafka引擎正确序列化Nested列?

我完全懂你的痛点——手动把对象数组转换成ClickHouse Nested列要求的属性分组格式,不仅麻烦还容易出错,尤其是有多个Nested字段的时候。其实不用手动硬写,不管是用System.Text.Json还是Newtonsoft.Json,都可以通过自定义序列化器来自动处理这个转换,甚至还有一些小技巧能简化操作。

下面分两种主流JSON库给你具体的实现方案:

一、使用System.Text.Json自定义转换器

我们可以写一个通用的JSON转换器,专门处理List<T>类型,把它自动转换成ClickHouse需要的Nested列格式。

步骤1:编写通用转换器

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

public class ClickHouseNestedListConverter<T> : JsonConverter<List<T>>
{
    public override List<T>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // 你的场景主要是序列化,反序列化可以按需实现,这里先抛出异常
        throw new NotImplementedException("Deserialization is not implemented for this converter.");
    }

    public override void Write(Utf8JsonWriter writer, List<T> value, JsonSerializerOptions options)
    {
        if (value == null || value.Count == 0)
        {
            writer.WriteStartObject();
            writer.WriteEndObject();
            return;
        }

        // 获取实体类的所有公共属性
        PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        
        writer.WriteStartObject();
        
        foreach (var prop in properties)
        {
            writer.WritePropertyName(prop.Name);
            writer.WriteStartArray();
            
            // 遍历每个对象,收集当前属性的所有值
            foreach (var item in value)
            {
                var propValue = prop.GetValue(item);
                JsonSerializer.Serialize(writer, propValue, options);
            }
            
            writer.WriteEndArray();
        }
        
        writer.WriteEndObject();
    }
}

步骤2:在模型中应用转换器

假设你的实体类是这样的:

public class Characteristic
{
    public uint char_id { get; set; }
    public string char_name { get; set; }
    public string char_value { get; set; }
}

public class ProductMessage
{
    // 给需要转换的List属性加上转换器特性
    [JsonConverter(typeof(ClickHouseNestedListConverter<Characteristic>))]
    public List<Characteristic> characteristics { get; set; }
    // 其他业务字段...
}

之后用JsonSerializer.Serialize(productMessage)就能自动输出ClickHouse需要的格式了。

二、使用Newtonsoft.Json自定义转换器

如果你更习惯用Newtonsoft.Json,思路完全一致,写一个对应的转换器即可:

步骤1:编写Newtonsoft转换器

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

public class ClickHouseNestedListConverterNewtonsoft<T> : JsonConverter<List<T>>
{
    public override bool CanRead => false;

    public override List<T>? ReadJson(JsonReader reader, Type objectType, List<T>? existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException("Deserialization is not implemented for this converter.");
    }

    public override void WriteJson(JsonWriter writer, List<T>? value, JsonSerializer serializer)
    {
        if (value == null || value.Count == 0)
        {
            writer.WriteStartObject();
            writer.WriteEndObject();
            return;
        }

        PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        
        writer.WriteStartObject();
        
        foreach (var prop in properties)
        {
            writer.WritePropertyName(prop.Name);
            writer.WriteStartArray();
            
            foreach (var item in value)
            {
                var propValue = prop.GetValue(item);
                serializer.Serialize(writer, propValue);
            }
            
            writer.WriteEndArray();
        }
        
        writer.WriteEndObject();
    }
}

步骤2:在模型中应用

public class ProductMessage
{
    [JsonConverter(typeof(ClickHouseNestedListConverterNewtonsoft<Characteristic>))]
    public List<Characteristic> characteristics { get; set; }
    // 其他业务字段...
}

用JsonConvert.SerializeObject(productMessage)就能得到符合要求的JSON了。

三、简化方案:手动封装辅助方法

如果不想写转换器,也可以写一个静态辅助方法,专门把对象数组转换成ClickHouse需要的结构:

public static class ClickHouseHelper
{
    public static Dictionary<string, object> ConvertToNestedFormat<T>(List<T> items)
    {
        if (items == null || items.Count == 0)
            return new Dictionary<string, object>();

        var result = new Dictionary<string, object>();
        var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var prop in properties)
        {
            var values = new List<object>();
            foreach (var item in items)
            {
                values.Add(prop.GetValue(item) ?? DBNull.Value);
            }
            result.Add(prop.Name, values);
        }

        return result;
    }
}

使用的时候直接转换再序列化:

var nestedCharacteristics = ClickHouseHelper.ConvertToNestedFormat(characteristicsList);
var message = new { characteristics = nestedCharacteristics /* 其他字段 */ };
var json = JsonSerializer.Serialize(message);

一些注意事项

  • 实体类中的Nullable类型(比如uint?),转换器和辅助方法已经能处理null值,无需额外修改。
  • 如果数据量很大,反射可能有性能开销,可以考虑缓存typeof(T)的属性信息,避免重复反射。
  • 多个不同的Nested字段,只需要给对应的List<T>属性应用同一个通用转换器即可,不用为每个类型单独写转换器。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 07:33:06