ServiceStack.Text serialize circular references

ⅰ亾dé卋堺 提交于 2019-12-10 19:59:38

问题


I need to serialize an object graph like this:

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

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

So that the json only gets two instances, but is deserialized correctly again. E.g. using a meta Id or something similiar.

I know that there is a way in Json.NET as described here: http://note.harajuku-tech.org/serializing-circular-references-with-jsonnet with meta ids.

Is there a similiar feature in ServiceStack.Text Json Serializer?

Otherwise, is it possible to use Json.NET in ServiceStack and how?

EDIT:

To make it clear, i ask for instance references, not just the same type. An example of this may be:

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

There are only 2 objects "really" serialized, the rest is reused using the $ref property field. Think of an object model having a collection of subitems. These subitems have a back-reference to their parent object. E.G. Customer/Order. One customer has multiple orders, each order has a reference to its customer. Now think of what happens, if you serialize one customer.

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

And you result in something similiar to this site's name. ;)

I really like ServiceStack for its clearity, not to require KnownTypeAttributes etc.

I would love to keep it that clean, without to implement custom loader/object initializers in my business logic pocos.


回答1:


I solved the problem by an alternative way. This actually works, but it could make problems later when using more complex data structures with multiple circular references. But for now there is no need.

I tried in adding the circular references feature to ServiceStack.Text but found no point to start at it. Maybe mythz could give me a hint? The feature should be really simple to be done.

I needed that feature for serialization of my data model to fully support NHibernate's merge function.

I followed mythz suggestion to just ignore the properties with the IgnoreDataMemberAttribute which cause the circular references. But this also requires to rebuild them after deserialization again, to get the merge feature working.

-> This is the solution, now follows how i did it:

I started with a simple prototype to test this, a data model of

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

Each class derives from the entity class.

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

As you can see, Order and OrderDetail have a back reference to it's parent objects, which caused the circular references when serialized. This can be fixed by ignoring the back reference with the IgnoreDataMemberAttribute.

My assumption now is, that every child instance of Order which is inside Customer's list property Orders has a back reference to this Customer instance.

So this is how i rebuild the circular tree:

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

This code does not work for 1->1 entity references for now (no need yet) but i assume it could be easily extended.

This now allows me to have a POCO class model at client, add/update/remove child objects and send the whole tree back to the server. Nhibernate is clever enough to determine, which entity is new/updated/removed. It also only updates the changed entity and only the changed property as well! It also removes all OrderDetails if an Order is removed.

Thats the fluent nhibernate mapping for completeness:

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() is used to let nhibernate only update the changed properties. You now only need to use the ISession.Merge(customer) function to save everything correctly.




回答2:


If anyone needs to be able to serialize object graphs with cycles, JSON.NET does support it:

new JsonSerializer
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects
};



回答3:


ServiceStack supports circular references by default.

Why not try this yourself first to verify if there's an actual issue before posting? This would take less effort than creating a new question and asking someone else to do it.

Following your example:

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();

Will print the JSON string:

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

Whilst serializing it to JSON and deserializing it back again like:

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

Will dump the contents:

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


来源:https://stackoverflow.com/questions/15138872/servicestack-text-serialize-circular-references

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!