【问题标题】:ServiceStack.Text serialize circular referencesServiceStack.Text 序列化循环引用
【发布时间】:2023-03-21 03:27:01
【问题描述】:

我需要像这样序列化一个对象图:

public class A
{
     public B Link1 {get;set;}
}

public class B
{
     public A Link2 {get;set;}
}

这样 json 只会得到两个实例,但会再次正确反序列化。例如。使用元 ID 或类似的东西。

我知道 Json.NET 中有一种方法,如下所述:http://note.harajuku-tech.org/serializing-circular-references-with-jsonnet with meta ids。

ServiceStack.TextJson Serializer 中是否有类似的功能?

否则,是否可以在ServiceStack 中使用 Json.NET 以及如何使用?

编辑:

为了清楚起见,我要求实例引用,而不仅仅是同一类型。 这方面的一个例子可能是:

[
    {
        "$id": "1",
        "BroId": 0,
        "Name": "John",
        "Bros": [
            {
                "$id": "2",
                "BroId": 0,
                "Name": "Jared",
                "Bros": [
                    {
                        "$ref": "1"
                    }
                ]
            }
        ]
    },
    {
        "$ref": "2"
    }
]

只有 2 个对象“真正”被序列化,其余对象使用 $ref 属性字段重用。 考虑一个具有子项集合的对象模型。这些子项具有对其父对象的反向引用。例如。客户订单。一个客户有多个订单,每个订单都有一个对其客户的引用。 现在想想如果你序列化一个客户会发生什么。

Customer
 -> Order
  -> Customer
   -> Order
    -> ...

而您的结果与该站点的名称相似。 ;)

我真的很喜欢 ServiceStack 的清晰性,不需要 KnownTypeAttributes 等。

我希望保持它的整洁,而不是在我的业务逻辑 pocos 中实现自定义加载器/对象初始化器。

【问题讨论】:

    标签: c# json.net servicestack circular-reference


    【解决方案1】:

    我用另一种方法解决了这个问题。 这确实可行,但在使用具有多个循环引用的更复杂的数据结构时可能会出现问题。但现在没有必要。

    我尝试将循环引用功能添加到ServiceStack.Text,但发现没有必要从它开始。也许mythz可以给我一个提示?该功能应该非常简单。

    我需要该功能来序列化我的数据模型以完全支持NHibernate 的合并功能。

    我遵循了mythz 的建议,只是忽略了IgnoreDataMemberAttribute 导致循环引用的属性。 但这也需要在再次反序列化后重新构建它们,以使合并功能正常工作。

    ->这是解决方案,现在按照我的做法:

    我从一个简单的原型开始测试这个,一个数据模型

    Customer 1->n Orders 1->n OrderDetail.

    每个类都派生自实体类。

    public class Customer : Entity
    {
        public virtual string Name { get; set; }
        public virtual string City { get; set; }
        public virtual IList<Order> Orders { get; set; }
    }
    
    public class Order : Entity
    {
        public virtual DateTime OrderDate { get; set; }
        public virtual IList<OrderDetail> OrderDetails { get; set; }
        [IgnoreDataMember]
        public virtual Customer Customer { get; set; }
    }
    
    public class OrderDetail : Entity
    {
        public virtual string ProductName { get; set; }
        public virtual int Amount { get; set; }
        [IgnoreDataMember]
        public virtual Order Order{ get; set; }
    }
    

    如您所见,OrderOrderDetail 对其父对象有一个反向引用,这会在序列化时导致循环引用。这可以通过忽略 IgnoreDataMemberAttribute 的反向引用来解决。

    我现在的假设是,Customer 的列表属性 Orders 内的每个 Order 子实例都有一个对该 Customer 实例的反向引用。

    这就是我重建循环树的方式:

    public static class SerializationExtensions
    {
        public static void UpdateChildReferences(this object input)
        {
            var hashDictionary = new Dictionary<int, object>();
            hashDictionary.Add(input.GetHashCode(), input);
    
            var props = input.GetType().GetProperties();
            foreach (var propertyInfo in props)
            {
                if (propertyInfo.PropertyType.GetInterfaces()
                    .Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
                {
    
                    var instanceTypesInList = propertyInfo.PropertyType.GetGenericArguments();
                    if(instanceTypesInList.Length != 1)
                        continue;
    
                    if (instanceTypesInList[0].IsSubclassOf(typeof(Entity)))
                    {
                        var list = (IList)propertyInfo.GetValue(input, null);
                        foreach (object t in list)
                        {
                            UpdateReferenceToParent(input, t);
                            UpdateChildReferences(t);
                        }
                    }
                }
            }
        }
    
        private static void UpdateReferenceToParent(object parent, object item)
        {
            var props = item.GetType().GetProperties();
            var result = props.FirstOrDefault(x => x.PropertyType == parent.GetType());
    
            if (result != null)
                result.SetValue(item, parent, null);
        }
    }
    

    此代码目前不适用于 1->1 实体引用(还不需要),但我认为它可以轻松扩展。

    这现在允许我在客户端拥有一个 POCO 类模型,添加/更新/删除子对象并将整个树发送回服务器。 Nhibernate 足够聪明,可以确定哪个实体是新的/更新的/删除的。它也只更新更改的实体和更改的属性!如果订单被删除,它也会删除所有 OrderDetails。

    为了完整性,这就是流畅的 nhibernate 映射:

    public class CustomerMap : ClassMap<Customer>
    {
        public CustomerMap()
        {
            Schema("YOURSCHEMA");
            Table("CUSTOMER");
            Id(x => x.Id, "ID").GeneratedBy.Assigned();
            Map(x => x.Name, "NAM");
            Map(x => x.City, "CITY");
            HasMany(x => x.Orders)
                .KeyColumn("CUSTOMER_ID")
                .Not.LazyLoad()
                .Inverse()
                .Cascade.AllDeleteOrphan();
    
    
            DynamicUpdate();
        }
    }
    
    public class OrderMap : ClassMap<Order>
    {
        public OrderMap()
        {
            Schema("YOURSCHEMA");
            Table("CUSTOMER_ORDER");
            Id(x => x.Id, "ID").GeneratedBy.Assigned();
            Map(x => x.OrderDate, "ORDER_DATE");
            HasMany(x => x.OrderDetails)
                .KeyColumn("ORDER_ID")
                .Not.LazyLoad()
                .Inverse()
                .Cascade.AllDeleteOrphan();
    
            References<Customer>(x => x.Customer, "CUSTOMER_ID");
            DynamicUpdate();
        }
    }
    
    public class OrderDetailMap : ClassMap<OrderDetail>
    {
        public OrderDetailMap()
        {
            Schema("YOURSCHEMA");
            Table("ORDER_DETAIL");
            Id(x => x.Id, "ID").GeneratedBy.Assigned();
            Map(x => x.ProductName, "PRODUCT_NAME");
            Map(x => x.Amount, "AMOUNT");
    
            References<Order>(x => x.Order, "ORDER_ID");
            DynamicUpdate();
        }
    }
    

    DynamicUpdate() 用于让 nhibernate 只更新更改的属性。 您现在只需要使用ISession.Merge(customer) 函数即可正确保存所有内容。

    【讨论】:

      【解决方案2】:

      如果有人需要能够用循环序列化对象图,JSON.NET 确实支持它:

      new JsonSerializer
      {
          PreserveReferencesHandling = PreserveReferencesHandling.Objects
      };
      

      【讨论】:

        【解决方案3】:

        ServiceStack 默认支持循环引用。

        为什么不先自己尝试一下,在发布之前验证是否存在实际问题?这比创建一个新问题并请其他人做这件事花费更少的精力。

        按照你的例子:

        public class A
        {
            public string Name { get; set; }
            public B Link1 { get; set; }
        }
        
        public class B
        {
            public string Name { get; set; }
            public A Link2 { get; set; }
        }
        
        var dto = new A { 
           Name = "A1", 
           Link1 = new B { Name = "B1", Link2 = new A { Name = "A2" } } 
        };
        dto.ToJson().Print();
        

        将打印 JSON 字符串:

        {"Name":"A1","Link1":{"Name":"B1","Link2":{"Name":"A2"}}}
        

        同时将其序列化为 JSON 并再次反序列化,如下所示:

        var fromJson = dto.ToJson().FromJson<A>();
        fromJson.PrintDump();
        

        将转储内容:

        {
            Name: A1,
            Link1: 
            {
                Name: B1,
                Link2: 
                {
                    Name: A2
                }
            }
        }
        

        【讨论】:

        • 您的示例 dto 没有使用循环引用 - 它为每个链接属性使用不同的对象。
        • :-) 同意。但这就是我认为 OP 所要求的。
        • 是的,这就是我想要的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-03
        • 2015-08-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多