|
Thanks!
Will try to reply again when it's done!
Or maybe announce it in the lounge as well!
|
|
|
|
|
|
Super Lloyd wrote: I might make a CodeProject article no not might - you must write an article!
|
|
|
|
|
Alright then!
|
|
|
|
|
Congratulations on your breakthrough !
I do wonder if you have really explored systematically what the WCF Serialization/Deserialization tools (DataContract, DataMember, etc.) can do.
For example, I can serialize/deserialize a WinForm TreeView TreeNodeCollection object by providing the necessary Type declarations and 'KnownType Attributes. I intend to publish this technique soon, since I have not seen it before (which doesn't mean it isn't "out there").
There is also a remarkable serializer presented in an article here on CodeProject, by Christophe Bertrand (2015), that is the only serializer I've seen that can serialize WinForm Controls as is [1] : [^].
[1] "as is:" i.e., not by creating a kind of "shadow class" that serializes only certain Properties, Fields, etc.
«There is a spectrum, from "clearly desirable behaviour," to "possibly dodgy behavior that still makes some sense," to "clearly undesirable behavior." We try to make the latter into warnings or, better, errors. But stuff that is in the middle category you don’t want to restrict unless there is a clear way to work around it.» Eric Lippert, May 14, 2008
|
|
|
|
|
Yes, yes I did!
One the problem with KnownTypeAttribute is that it disallow plugin, code that are not know when the base class is written.
Alright then, what about DataContractResolver then?
Well it helps... but one still need to put DataContract and DataMember attribute on everything that need be serialized otherwise it will be (sometimes) omitted.
Further as we used it at work, we kept stumbling on bug where some (new) subclass where not serialized properly for lack of proper DataContract and DataMember annotation.
To summarise the second problem with DataContractSerializer , it is far from idiot proof!
On that Newtonsoft.Json seems like a better deal. The development constraints it imposes are minimal and, with some settings, it supports circular reference and strongly typed serialization.
However it is way too chatty!
Finally, using easy to define surrogate classes I can even serialize such things as Stream, Bitmap, etc...
(I did define DateTime, TimeSpan, Tuple<1,2...> surrogates)
And it also supports IList, IList<T>. IDictionary, IDictionary<K, V>
Contrast that with one of my test method:
public class AAA
{
public int ID { get; set; }
public string Name { get; set; }
public List<int> Heights { get; set; }
public List<int> PreviousHeights { get; set; }
public DayOfWeek Day { get; set; }
public AAA Child { get; set; }
public Tuple<int, string> Tuple { get; set; }
public List<object> Objects { get; set; } = new List<object>();
}
[Fact]
public void SimpleSerialTest()
{
var aaa = new AAA()
{
ID = 73,
Name = "hello",
Heights = new List<int> { 1, 2, 4, 8, 16 },
Day = DayOfWeek.Sunday,
Tuple = Tuple.Create(42, "42"),
};
aaa.Child = aaa;
aaa.Objects.Add(aaa);
aaa.Objects.Add("hello");
var sb = new StringBuilder();
var pw = new PrimitiveTextWriter(new StringWriter(sb));
var ow = new ObjectWriter(pw);
ow.Write(aaa);
var s = sb.ToString();
var pr = new PrimitiveTextReader(new StringReader(s));
var or = new ObjectReader(pr);
var o = or.Read();
Assert.IsType<AAA>(o);
var aaa2 = (AAA)o;
Assert.Equal(aaa.ID, aaa2.ID);
Assert.Equal(aaa.Name, aaa2.Name);
Assert.Equal(aaa.Day, aaa2.Day);
Assert.Equal<int>(aaa.Heights, aaa2.Heights);
Assert.Equal<int>(aaa.PreviousHeights, aaa2.PreviousHeights);
Assert.Equal(aaa.Tuple, aaa2.Tuple);
Assert.Equal<object>(aaa.Objects, aaa2.Objects);
}
No work, no particular setup was required to save the data.
Alright, I lied, there is one setup call required, types must be "registered" with a call like
KnownTypes.Register(GetType().Assembly);
For the record this serialization method produce the text below. While largely bloated with Type meta data... it will win in my scenario where I have just a few types, long lists with lot of instances
Output (Text or Binary output is supported, here is the text ouput)
24 25 "Galador.Core.Tests.SerializationTest+AAA" 0 0 1 0 0 0 8 "ID" 11 "Name" 2 "Heights" 26 "System.Collections.Generic.List`1[[System.Int32]]" 2 0 1 0 11 0 1 "Capacity" 11 0 "PreviousHeights" 26 "Day" 27 "System.DayOfWeek" 98 0 1 0 11 0 0 0 "Child" 25 "Tuple" 28 "System.Tuple`2[[System.Int32],[System.String]]" 0 29 "Galador.Core.Serialization.TuppleSurrogate`2[[System.Int32],[System.String]]" 0 0 1 0 0 0 2 "Item1" 11 "Item2" 2 0 "Objects" 30 "System.Collections.Generic.List`1[[System.Object]]" 2 0 1 0 1 0 1 "Capacity" 11 0 0 73 31 "hello" 32 26 8 5 1 2 4 8 16 0 0 24 33 28 42 34 "42" 35 30 4 2 24 31
modified 22-Jun-16 5:23am.
|
|
|
|
|
I really appreciate your lengthy reply, and your comments on your experience with WCF DataContract.
I'd like to join in with the other comments here, and express my hope you will write a CP article on your work !
fyi: the XML output of the WCF DataContract serializer is "bloated" with, imho, bizarrely long internal field/tag-names: that's why I have gotten in the habit of GZiping it going and coming ... typical reduction in file size greater than 70% in my experience.
imho, one of the best "acid tests" for a serializer is self-referential types like a TreeNode that contains a pointer to its Parent Node, a list of pointers to its child nodes ... and, in the case of WinForms TreeView/TreeNodes, a pointer to the TreeView Control itself.
cheers, Bill
«There is a spectrum, from "clearly desirable behaviour," to "possibly dodgy behavior that still makes some sense," to "clearly undesirable behavior." We try to make the latter into warnings or, better, errors. But stuff that is in the middle category you don’t want to restrict unless there is a clear way to work around it.» Eric Lippert, May 14, 2008
|
|
|
|
|
Your serializer looks quite easy to use.
The output is human readable, but does not look to be human 'intuitive'. To get the data even more compact, maybe nice to have an option to compress (GZipStream?) the output since it does not look like anyone would ever 'hand edit' the serialized output, correct?? What am I missing?
Look forward to your CP article!
Curtis
|
|
|
|
|
The Serializer write to an interface I created call IPrimitiveWriter
I provide 2 implementation of IPrimitiveWriter
public class PrimitiveBinaryWriter : IPrimitiveWriter
{
public PrimitiveBinaryWriter(Stream stream)
}
public class PrimitiveTextWriter : IPrimitiveWriter
{
public PrimitiveTextWriter(TextWriter writer)
}
So... binary output is covered. And it's also forward only (unlike first version, haha), so the stream can be a GZipStream.
The text output is there mostly for fun. It also provide a tiny help for debugging and help me check there is no repetition (all reference, including string should be there only once)
|
|
|
|
|
Great! Looking forward to trying it after your CP Article.....
|
|
|
|
|
|
Super Lloyd wrote: since I shared plenty of reflection extension method....
An article in itself. I've been having fun with reflection and LINQ, but I'd really like to see some cool reflection extension methods, as well as that serializer that you describe.
Marc
|
|
|
|
|
Not something which struck my mind initially...
But now that you mention I have handy methods to try to call a constructor or a method... using a list of parameters... and they match using default value as well!
Which I haven't seen very often!
(using LINQ magic it's just one BIG statement! haha!)
Might do a tips / trick for those instead.. less work!
Other than those astute methods (TryConstruct() TryInvoke() TryConvert()) the rest is various very simple method to plug the hole in the PCL version of Reflection.
|
|
|
|
|
In fact I also have another serialization utility.
I didn't think of it since it has nothing to do with the serializer.
But it let you write something this handy!
doNotCollect = PropertyPath.Watch(aValue, x => x.A.B.C.D, newD => { });
|
|
|
|
|
|
Versioning support:
Can it read a stream written by an older version, with well-defined and useful behavior for the properties not present in the stream?
Can it read a stream written by a newer version that contains properties it does not understand?
Another problem I've commonly ran into is class invariants: they must be guaranteed when the class is properly constructed, but during serialization, it is not.
Typically, they are enforced through getters/setters - but if a serializer initializes an object through those, it can encounter incorrect intermediate states.
If constraints are not checked during read, then you need a separate function validating the object, duplicating the code in the getters / setters.
I haven't encountered a enjoyable solution to that problem that did not end with "well, then you have to change how your class is implemented".
Or, to snowclone a wonderful quote: The amount of serializers available is a sure sign that none of them really works.
|
|
|
|
|
Hey,
The intent of this serializer is to replace JsonSerializer (NOT the .NET Serializer) (i.e it's a document serialize, won't work on WPF/WinForm class, unfortunately...) while being way more compact, support IList, IDictionary (normal and generic version), be strongly typed yet type error tolerant. I also dropped human readability format. Text output is just for (painful) debugging purpose.
It also DOES NOT support delegates.
It is, indeed, not perfect....
Also it doesn't support TypeConverter / ValueConverter / ISerializable , but I am thinking to add support for them...
So to address each point in detail:
- by default it only serialize public fields and property. This can be modified with attribute on the class and/or field/property.
- what about versioning? It doesn't care if the property is missing while deserializing it is ignored
- class invariant... Right now one can enumerate all reference object deserialized with the ObjectContext of the deserializer. I was thinking / maybe to go one step further and create an IDeserialized interface to automatically "awake / complete" objects after deserialization
- also difficult type to serialize can be helped with an "easy" to implement ISurrogate<> class, define this class to replace problematic type when serializing / deserializing
public interface ISurrogate<T> {
void Initialize(T value);
T Instance();
}
- thinking of WPF.. I just realized that some readonly property (such as Children UI component) while being readonly, can still be serialized as an IList , should be fixed by publication time!
Forget that, it cause too much ambiguity about the serizalizer, since it can also (optionally) serialize private field.
modified 22-Jun-16 16:58pm.
|
|
|
|
|
Soudns good to me `IDeserialized` would allow to cope with many common problems I've encountered. `ISurrogate` could nicely isolate some complexity from the class / serializer itself.
Your striked-out part reminds me of another thing: read only Collections (they are managed internally) where the elements are mutable and should be serialized.
This could be handled in IDeserialized if I could construct thecollection there and then request serialization for the collection members.
|
|
|
|
|
Great to know IDeserialized works out for you!
I needed such concept for my IoC+IServiceProvider to completely initialize object tree (it's called IRegistryDelegate for that). Came with that there first!
After all the comments I decided to add yet more functionalities and now it's all broken....
Plus, exceptionally, I work this Saturday! (this my home project, not the work one)
So it's gonna be delayed some more...
But the current (broken) version does support ISerializable (but not [SerializableAttribute] which assume private field reading), TypeConverter (but not ValueSerializer which are not PLC / .NET Core) and it also support restoring public readonly reference property, such as:
public List<T> Children { get; } = new List<T>();
public Configuration Configuration { get { return existingConfig; } }
I was misleaded, proved easy and helpful!
ETA should be end of next week now...
|
|
|
|
|
Super Lloyd wrote: So it's gonna be delayed some more...
No worries, I'll be on vacation for a week.
(I expect it on my desk when I return, 9am point )
|
|
|
|
|
9am sharp! alright!
|
|
|
|
|
|
I'm impressed!
The introduction on GitHub already looks good - haven't dug deeper yet, though.
|
|
|
|
|
Thanks!
Just committed some performance improvements!
CodeProject article is next... might take a while...
|
|
|
|
|
|