ServiceStack.Text序列化循环引用

时间:2013-02-28 15:07:10

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

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

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

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

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

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

ServiceStack.Text Json 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的清晰度,而不是要求KnownTypeAttribute等。

我希望保持干净,不要在我的业务逻辑pocos中实现自定义加载器/对象初始化器。

3 个答案:

答案 0 :(得分:4)

我通过另一种方式解决了这个问题。 这实际上有效,但是当使用带有多个循环引用的更复杂的数据结构时,它可能会出现问题。但是现在没有必要。

我尝试将循环引用功能添加到ServiceStack.Text但是没有任何意义从此开始。也许mythz可以给我一个提示?该功能应该非常简单。

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

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

<强> - &GT;这是解决方案,现在遵循我的方式:

我从一个简单的原型开始测试,这是一个

的数据模型

Customer 1-&gt; n Orders 1-&gt; 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忽略后引用来解决。

我现在的假设是,位于Order列表属性Customer内的Orders的每个子实例都有对此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&gt; 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)功能正确保存所有内容。

答案 1 :(得分:1)

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

new JsonSerializer
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects
};

答案 2 :(得分:0)

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
        }
    }
}