问题
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 KnownTypeAttribute
s 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