NOTICE:
Months ago, I ran a performance test against several JSON serializers and found that:
- fastJSON was actually not so fast at all.
- Newtonsoft's JSON serializer had developed somewhat and its performance was enhanced, quite closed to fastJSON
- The fastest JSON serializer was NetJSON which used dynamic assemblies to boost the performance to the edge. The architecture of fastJSON would never lead it to be a counterpart to NetJSON.
Hereby, I had deprecated the synchronization with fastJSON and the development of PowerJSON.
The article was left here for its historical reasons. I recommend NetJSON as it is the fastest JSON serializer today.
Feb 3rd, 2018
Contents
- Introduction
- Background
- Basic Usage
- Transforming Data Before Serialization and Deserialization
- Serializing Unsupported Types with JsonConverter
- Conditionally Intercepting Serialization and Deserialization
- What about Performance
- History
This is a fork, an enhanced version, of Mehdi Gholam's brilliant project fastJSON. It adds some new classes, interfaces and custom attributes to facilitate JSON serialization and deserialization of objects. It also fixes some issues in the original version.
Although many features have been added to the original version, the performance is not sacrificed but even improved after thorough optimizations and refactoring.
The fork can be accessed on CodePlex at https://github.com/wmjordan/PowerJSON.
If fastJSON is new to you, it is recommended that you read this article before you begin.
PowerJSON is forked from fastJSON and is almost fully compatible with it. Therefore, the assembly file name of PowerJSON is fastJSON.dll and the type library uses <span class="literal">fastJSON</span>
as the namespace.
Besides the long features list of the original fastJSON, PowerJSON provides the following advanced features:
- Serialize member names in camel-case or uppercase, without forcing your data models to break the .NET coding convention merely to fit JSON protocols.
- Assign another name for the member in serialization.
- Polymorphic serialization without turning on the
UseExtensions
setting: Serialize and deserialize interface
instances rather than concrete class types; serialize and deserialize abstract
classes. - Assign another literal name for an
Enum
value. - Include a specific read-only member during serialization, or exclude a specific
public
member from being serialized. - Serialize and deserialize
private
/internal
classes, structs, fields or properties. - Transform data before serialization (for instance, encrypt the serialized data, serialize arrays into comma separated
string
s, etc.) and vice versa before deserialization. - Conditionally serialize or deserialize members.
- Write out additional data in serialization.
- Transform or validate data before (or after) serialization or deserialization.
- Invasiveless control of serialization and deserialization--altering the serialization result without changing the data model, which is especially helpful when you don't have the source code for them.
- Serializing
HashSet<T>
and other enumerable types which have an Add
(?) method. - Easiest customized serialization and deserialization. You can implement serialization for unsupported types with the least effort.
- A documentation file for the library.
This fork also fixes some issues in the original fastJSON, such as TimeSpan
instances could cause stack overflow, multi-value items in NameValueCollection
could not be deserialized as before serialization, etc. Please refer to the readme.md file in the source code for more details.
The following section will describe how to use PowerJSON to control the serialization result.
A Sample Class
One of the most common issues of JSON serialization is the naming of JSON structures and our .NET types. Given that we have a class like the following:
internal class DemoClass
{
public string MyProperty { get; set; }
public MyEnum MyEnumProperty { get; set; }
public int Number { get; set; }
public object Identifier { get; set; }
public int InternalValue { get; set; }
private int privateField;
}
public enum MyEnum
{
None,
Vip
}
public class ClassA
{
public string Name { get; set; }
}
public class ClassB
{
public int Code { get; set; }
}
And we want to fulfill the following requirements:
DemoClass
is an internal class
, which, by default, is NOT serializable for its constructor is not publicly visible, but we want to make it deserializable. - All properties should have camel-case serialized names.
MyProperty
is serialized to the name "prop
". MyEnumProperty
is serialized to have a name "enum
". Number
should not be serialized if its value is 0
. Identifier
can be of type ClassA
or ClassB
, and have a serialized name as "a
" or "b
", respectively. If Identifier
is not of the above two types, its serialized name should be "variant
". InternalValue
should not be serialized or deserialized. privateField
should be serialized. - The name of the
Vip
field in MyEnum
type should be serialized as "VIP
".
The above requirements could not be fulfilled with fastJSON, but with PowerJSON, it is possible, and there are two ways.
- Invasive Mode: Add Custom Attributes to the data model, i.e.,
DemoClass
here, to control the serialization. - Noninvasive Mode: Leave the data model along. Programmatically control the serialization result.
Invasive Mode: Controlling Serialization with Custom Attributes
The invasive mode of serialization control adds custom attributes to the classes or structs we want to take control.
Since this approach touches the source code, it is called Invasive Mode.
The following code shows how to use custom attributes to control the serialization result.
[JsonSerializable]
internal class DemoClass
{
[JsonField ("prop")]
public string MyProperty { get; set; }
[JsonField ("enum")]
public MyEnum MyEnumProperty { get; set; }
[JsonNonSerializedValue (0)]
public int Number { get; set; }
[JsonField ("a", typeof (ClassA))]
[JsonField ("b", typeof (ClassB))]
[JsonField ("variant")]
public object Identifier { get; set; }
[JsonInclude (false)]
[System.ComponentModel.ReadOnly (true)]
public int InternalValue { get; set; }
[JsonSerializable]
private int privateField;
}
public enum MyEnum
{
None,
[JsonEnumValue ("VIP")]
Vip
}
After annotating the data model with custom attributes, we can serialize the data model.
Firstly, setup the default serialization settings to fulfill this requirement--all properties should have camel-case serialized names.
JSON.Parameters.NamingConvention = NamingConvention.CamelCase;
JSON.Parameters.UseExtensions = false;
Secondly, initialize the data model with some values.
var d = new Demo1.DemoClass () {
MyProperty = "p",
Number = 1,
MyEnumProperty = Demo1.MyEnum.Vip,
InternalValue = 2,
Identifier = new ClassA () { Name = "c" }
};
Finally, call the ToJSON
method to get the serialization result.
var s = JSON.ToJSON (d);
Console.WriteLine (s);
The above example gives the following result.
{"prop":"p","enum":"VIP","number":1,"a":{"name":"c"},"privateField":0}
To deserialize the JSON string
back to an instance of DemoClass
, use the ToObject<span id="LST5A47910_0"><T<span id="LST5A47910_1">></span></span>
method.
var o = JSON.ToObject<Demo1.DemoClass> (s);
Noninvasive mode: Controlling Serialization with SerializationManager
The invasive mode is quite simple--simply marking the classes and members with custom attributes and things are done. However, there are some disadvantages of custom attributes. Some of them are listed below:
- Issue 1: Custom attributes require modifications on source code, but sometimes it is impossible, e.g., we can't modify the CLR types in the .NET Framework.
- Issue 2: They invade the data models and make them rely on the PowerJSON library.
- Issue 3: They may conflict, typically when the same data model is required to be serialized to various forms.
To surmount the above issues, PowerJSON has introduced a Noninvasive Mode of serialization control. The noninvasive mode makes no modification on the data models, yet provides no less power than the invasive mode.
The noninvasive mode is majorly implemented with the following classes: SerializationManager
, TypeOverride
and MemberOverride
.
The default instance of SerializationManager
can be access from the static Manager
property in the JSON
class. Calling its Override<span id="LST5A47910_4"><T<span id="LST5A47910_5">></span></span>
method, which takes an instance of TypeOverride
, will tell the serialization engine how to alter the result of serialization or deserialization.
The following code gives a usage example of using SerializationManager
.
JSON.Manager.Override<DemoClass> (new TypeOverride () {
Deserializable = true,
MemberOverrides = {
new MemberOverride ("MyProperty", "prop"),
new MemberOverride ("MyEnumProperty", "enum"),
new MemberOverride ("Number") { NonSerializedValues = { 0 } },
new MemberOverride ("Identifier", "variant") {
TypedNames = {
{ typeof(ClassA), "a" },
{ typeof(ClassB), "b" }
}
},
new MemberOverride ("InternalValue") {
Deserializable = false,
Serializable = false
},
new MemberOverride ("privateField") {
Serializable = true,
Deserializable = true
}
}
});
JSON.Manager.OverrideEnumValueNames<MyEnum> (new Dictionary<string, string> {
{ "Vip", "VIP" }
});
To serialize or deserialize the data model, use the same function call as the invasive mode to the JSON
class. And the output will be the same as the invasive mode example.
With the SerializationManager
, serialization can be controlled from external code. Issue 1 and Issue 2 is resolved. To solve Issue 3, conflictive serializations, we will use an alternating SerializationManager
, which is discussed below.
Alternating the SerializationManager
To demonstrate why custom attributes may conflict and how to resolve the conflict with the alternation of SerializationManager
, let's take a look at the following classes first.
public class Group
{
public int ID { get; set; }
public string Name { get; set; }
public List<Member> Members { get; private set; } = new List<Member>();
}
public class Member
{
public int GroupID { get; set; }
public string Name { get; set; }
}
The above code snippet has two classes. A Group
class has a list containing multiple instances of Member
class.
A normal serialization of the Group
class may look like this (whitespaces added for clarity):
{"ID": 1,
"Name": "group name",
"Members": [
{ "GroupID": 1, "Name": "a" },
{ "GroupID": 1, "Name": "b" },
{ "GroupID": 1, "Name": "c" }
]}
We can see that the "GroupID":1 is repeated multiple times in each serialized Member
instance. Since all members belong to the group 1, which is already represented in "ID": 1 and implied by the cascading structure of the JSON result, those "GroupID": 1 fields could be omitted for conciseness.
So we may hope that:
- The
GroupID
fields should be hidden when they are serialized within a Group
instance to generate a compact, unredundant result. - The
GroupID
fields should be visible when they are individually serialized within the Member
instance.
{"ID": 1,
"Name": "group name",
"Members": [
{ "Name": "a" },
{ "Name": "b" },
{ "Name": "c" }
]}
{"GroupID":1, "Name":"a"}
Adding serialization control onto the GroupID
member of the Member
class can hide the GroupID
field, however this also hides it from the individual serialization result of the Member
class, generating a result like this, which is undesirable.
{"Name":"a"}
To fulfill both serialization requirements of the Group
and Member
classes, we shall use an alternating SerializationManager
and pass it into the ToJSON(Object, JSONParameters, SerializationManager)
method overload which takes a SerializationManager
as a parameter.
The alternating SerializationManager
passed to the ToJSON
method can override the Group
class to hide the GroupID
field, and the default one can be used to serialize the Member
class, which reveals the GroupID
field by default, like the following code shows.
var g = new Group () {
ID = 1,
Name = "test",
Members = {
new Member () { GroupID = 1, Name = "a" },
new Member () { GroupID = 1, Name = "b" },
new Member () { GroupID = 1, Name = "c" }
}
};
var gsm = new SerializationManager ();
gsm.Override<Member> (new TypeOverride () {
MemberOverrides = { new MemberOverride ("GroupID", false) }
});
var s1 = JSON.ToJSON (g, JSON.Parameters, gsm);
Console.WriteLine ("Group: " + s1);
Assert.IsFalse (s1.Contains ("GroupID"));
s1 = JSON.ToJSON (g.Members[0]);
Console.WriteLine ("Member: " + s1);
StringAssert.Contains (s1, "GroupID");
The above code outputs the following result, which is what we need:
Group: {"ID":1,"Name":"test","Members":[{"Name":"a"},{"Name":"b"},{"Name":"c"}]}
Member: {"GroupID":1,"Name":"a"}
Transforming Data Before Serialization and Deserialization
During cross-platform communication, we sometimes may want to transform our data to other forms to suite the need on other platforms. For instance,
- Encrypting critical information in the JSON.
- Converting
Array
s to comma separated values. - Different presentation of
DateTime
values. - Serialize
Enum
values to numbers.
Ordinary serializers may require you to create a data model or add extra fields and serialize them. With PowerJSON, no modification on the data model is required. This keeps the original model clean and fits the requirements at the same time. This is done with the feature of JSON Converter.
We shall firstly write a class to implement the IJsonConverter
interface which will be used before serialization and deserialization.
Before serialization, the SerializationConvert
method of that interface will be called, with the name and the value of the object being serialized. The corresponding parameters are stored in an instance of JsonItem
. Before JSON deserialization, DeserializationConvert
will be called, and the JsonItem
will be passed into the method as well.
The converter can change the Value
of the JsonItem
instance in the above methods, thus the serialized value can be a different type from the original data model.
Data Transformation, Encryption
The following code demonstrates how IJsonConverter
works. During serialization, the string
to be encrypted (item.Value
) will be passed into SerializationConvert
. And this class fakes an "encryption" by prefixing "Encrypted: " before the string
. In deserialization, the encrypted string
will be passed into DeserializationConvert
and the method fakes a "decryption" by removing the prefix.
class FakeEncryptionConverter : IJsonConverter
{
public Type GetReversiveType (JsonItem item) {
return null;
}
public void SerializationConvert (JsonItem item) {
var s = item.Value as string;
if (s != null) {
item.Value = "Encrypted: " + s;
}
}
public void DeserializationConvert (JsonItem item) {
var s = item.Value as string;
if (s != null && s.StartsWith ("Encrypted: ")) {
item.Value = s.Substring ("Encrypted: ".Length);
}
}
}
When the converter is ready, we can apply the converter onto any string
fields we want to "encrypt". Before serialization, the FakeEncryptionConverter
will "encrypt" MyProperty
to produce the serialization result, and "decrypt" it before deserialization. The class DataConverterTestSample
is completely innocent of all this behind, and simply does its job as if MyProperty
is never been "encrypted" or "decrypted".
public class DataConverterTestSample
{
[JsonConverter (typeof(FakeEncryptionConverter))]
public string MyProperty { get; set; }
}
We can implement the IJsonConverter
to convert different data types between serialization and deserialization. For example, the following definition uses Int32ArrayConverter
to convert int
arrays into string
s before serialization, and vice versa before deserialization.
public class CustomConverterType
{
[JsonConverter (typeof (Int32ArrayConverter))]
[JsonField ("arr")]
public int[] Array { get; set; }
public int[] NormalArray { get; set; }
[JsonConverter (typeof (Int32ArrayConverter))]
[JsonField ("intArray1", typeof (int[]))]
[JsonField ("listInt1", typeof (List<int>))]
public IList<int> Variable1 { get; set; }
[JsonConverter (typeof (Int32ArrayConverter))]
[JsonField ("intArray2", typeof (int[]))]
[JsonField ("listInt2", typeof (List<int>))]
public IList<int> Variable2 { get; set; }
}
class Int32ArrayConverter : IJsonConverter
{
public Type GetReversiveType (JsonItem item) {
return null;
}
public void DeserializationConvert (JsonItem item) {
var s = item.Value as string;
if (s != null) {
item.Value = Array.ConvertAll (s.Split (','), Int32.Parse);
}
}
public void SerializationConvert (JsonItem item) {
var l = item.Value as int[];
if (l != null) {
item.Value = String.Join (",", Array.ConvertAll (l, Convert.ToString));
}
}
}
Sample input of the above code is:
var c = new CustomConverterType () {
Array = new int[] { 1, 2, 3 },
NormalArray = new int[] { 2, 3, 4 },
Variable1 = new int[] { 3, 4 },
Variable2 = new List<int> { 5, 6 }
};
And the output is:
{"arr":"1,2,3","NormalArray":[2,3,4],
"intArray1":"3,4","listInt2":[5,6]}
Please note: During deserialization, the value of JsonItem
is not fully deserialized and converted to the type of the property yet. The value before fully deserialized has one of the primitive deserialization types. There are six possible primitive deserialization types: Boolean
for boolean values, Int64
for integers, Double
for fractional numbers (decimal
type is also converted into Double
), String
for literal values, enum
, DateTime
and TimeSpan
types, IList<Object>
for arrays, and IDictionary<string, object>
for general class or structs, and of course, null
values are also possible. The Value
of the JsonItem
passed into DeserializationConvert
method could be one of the above six types.
Nonetheless, you don't have to care about primitive deserialization types during serialization. The serializer can handle the conversion.
To avoid handling primitive deserialization types, the implementation of the IJsonConverter
should write a GetReversiveType
method, which tells the deserialization engine the expected type of the DeserializationConvert
method, and the deserializer will try to convert the primitive value to the type returned from the GetReversiveType
method.
For example, you are trying to serialize a DateTime
property adding one hour for the daylight-saving time and subtract one hour while deserializing it. Without giving the type from the GetReversiveType method, the code will look like this:
class DayLightSavingTimeConverter : IJsonConverter {
public Type GetReversiveType (JsonItem item) {
return null;
}
public void SerializationConvert (JsonItem item) {
if (item.Value is DateTime) {
item.Value = ((DateTime)item.Value).AddHours(1);
}
}
public void DeserializationConvert (JsonItem item) {
DateTime d = DateTime.Parse((String)item.Value, System.Globalization.CultureInfo.InvariantCulture);
item.Value = d.AddHours(-1);
}
}
To simplify the code, we have the GetReversiveType
method return the typeof(DateTime)
, to tell the deserializer the expected data type is DateTime
, like this:
class DayLightSavingTimeConverter : IJsonConverter {
public Type GetReversiveType (JsonItem item) {
return typeof(DateTime);
}
public void SerializationConvert (JsonItem item) {
if (item.Value is DateTime) {
item.Value = ((DateTime)item.Value).AddHours(1);
}
}
public void DeserializationConvert (JsonItem item) {
DateTime d = (DateTime)item.Value;
item.Value = d.AddHours(-1);
}
}
The primitive deserialization types may have you feel a bit cumbersome to write the DeserializationConvert
part. Fortunately, if you have the converter class inherit from the JsonConverter<>
class, the deserializer will internally detect the possible type (the converted type, rather than the original type) during deserialization, and try its best to convert the primitive deserialization type to suite the need of the DeserializationConvert
method.
Let's see how it works by comparing to the above code.
class DateConverter : JsonConverter<DateTime, DateTime>
{
protected override DateTime Convert (string fieldName, DateTime fieldValue) {
return fieldValue.AddHours (1);
}
protected override DateTime Revert (string fieldName, DateTime fieldValue) {
return fieldValue.AddHours (-1);
}
}
The DateConverter
is based on a generic helper class JsonConverter<>
, which implements the IJsonConverter
interface, converting between two specific member types during conversion.
The JsonConverter<>
class has two protected
abstract
methods: Convert
and Revert
. Convert
is called before serialization. It takes the name and value of the member being serialized. The return value will be serialized to the JSON output. Revert
is called before deserialization and the return value will be used as the result of deserialization, or set to the field or property of the deserialized object.
When implementing those two methods, you can directly convert between .NET data types rather than dealing with primitive JSON types.
With the JsonConverter<>
, it is also possible to specifically serialize a certain enum
types into numbers without turning on the UseValuesOfEnums
setting in JSONParameters
, which affects all enum
types.
The following code demonstrates how it works. The class below has two properties of enum
types. Assuming that we want the first one to be serialized as usual--the value is serialized into the literal form, but the second one is serialized into the numeric form. We can use the JsonConverterAttribute
to mark the second property. And then we implement the NumericEnumConverter
, inheriting from the JsonConverter<>
class.
public class EnumTestSample
{
public Fruits MyFruit { get; set; }
[JsonConverter (typeof (NumericEnumConverter))]
public Fruits NumericFruit { get; set; }
}
public class NumericEnumConverter : JsonConverter<Fruits, int>
{
protected override int Convert (string fieldName, Fruits fieldValue) {
return (int)fieldValue;
}
protected override Fruits Revert (string fieldName, int fieldValue) {
return (Fruits)fieldValue;
}
}
With the above code ready, the second property NumericFruit
will be serialized into a numeric form.
We can do more complex type conversion, like the following code shows. The code will have a string
-typed property to be serialized into a JSON structure defined in a class
, PersonInfo
.
public class PersonInfo
{
public string Name { get; set; }
public bool Vip { get; set; }
}
public class CustomConverterType
{
[JsonConverter (typeof (PersonInfoConverter))]
public string Master { get; set; }
[JsonConverter (typeof (PersonInfoConverter))]
public string Worker { get; set; }
}
class PersonInfoConverter : JsonConverter<string, PersonInfo>
{
protected override PersonInfo Convert (string fieldName, string fieldValue) {
return new PersonInfo () {
Name = fieldValue.EndsWith ("*") ?
fieldValue.Substring (0, fieldValue.Length - 1) : fieldValue,
Vip = fieldValue.EndsWith ("*")
};
}
protected override string Revert (string fieldName, PersonInfo fieldValue) {
return fieldValue.Name + (fieldValue.Vip ? "*" : null);
}
}
With the above definition, we create an instance like the following code:
var d = new CustomConverterType() { Master = "WMJ*", Worker = "Gates" };
The serialized result can look like this:
{"Master":{"Name":"WMJ","Vip":true},
"Worker":{"Name":"Gates","Vip":false}}
Without the help of the JsonConverterAttribute
, the serialization result would be this:
{"Master":"WMJ*","Worker":"Gates"}
Although PowerJSON has added supports for serializing more types. It could not yet cover all data types in the practical world. Various JSON serializers has provided mechanisms to support customized serialization. fastJSON provided Serialize
and Deserialize
delegates, JSON.net has also provided an abstract JsonConverter
class for this purpose (see this API documentation). However, in all those mechanisms, you have to maintain the format of the JSON when implementing customized serialization, and deal with primitive types in deserialization. With the JsonConverter
in PowerJSON, you don't need to care about those issues. Just focus on the data itself is enough, the rest will be handled by the serialization engine.
Firstly let's take a look at the source code of JSON.net and see how it implements a custom JsonConverter
which serialize and deserialize the Version
class.
public class VersionConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null) {
writer.WriteNull();
}
else if (value is Version) {
writer.WriteValue(value.ToString());
}
else {
throw new JsonSerializationException("Expected Version object value");
}
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) {
return null;
}
else {
if (reader.TokenType == JsonToken.String) {
try {
Version v = new Version((string)reader.Value);
return v;
}
catch (Exception ex) {
throw JsonSerializationException.Create(reader,
"Error parsing version string: {0}".FormatWith
(CultureInfo.InvariantCulture, reader.Value), ex);
}
}
else {
throw JsonSerializationException.Create(reader,
"Unexpected token or value when parsing version.
Token: {0}, Value: {1}".FormatWith(CultureInfo.InvariantCulture,
reader.TokenType, reader.Value));
}
}
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(Version);
}
}
The above code is very long and you see that it requires you to learn about JsonReader
, JsonWriter
, JsonSerializer
, JsonToken
types. With PowerJSON, the conversion support for Version
class, although was not supported internally, can be implemented easily without any knowledge of the implementation of the JSON serialization engine.
public class VersionConverter : JsonConverter<Version, string>
{
protected override string Convert (string fieldName, Version fieldValue) {
return fieldValue != null ? fieldValue.ToString () : null;
}
protected override Version Revert (string fieldName, string fieldValue) {
try {
return fieldValue != null ? new Version (fieldValue) : null;
}
catch (Exception) {
throw new JsonSerializationException ("Error parsing version string: " + fieldValue);
}
}
}
You can see that you don't need to think about WriteNull
, WriteValue
, TokenType
, (string)reader.Value
, etc. The only thing needed is the code to convert between the Version
and string
types. Once the conversion code is written, it is done. JsonConverter
will automatically convert the object and JSON string back and forth.
To apply the converter, add one line before the first serialization and deserialization begins and the VersionConverter
will be applied to all instances of the Version
class.
JSON.Manager.OverrideConverter<Version> (new VersionConverter ());
Run the following code and the output will be "1.2.3.1234"
(with quotation marks).
var v = new Version (1, 2, 3, 1234);
var s = JSON.ToJSON (v);
Console.WriteLine (s);
The above case is quite simple since the type is serialized to a string
. The next example is a little more complex, which serializes Regex
.
The Regex
class could not be normally serialized or deserialized, since it does not expose a property for the pattern, which is essential to create the instance, nor does it provide a parameterless constructor, which is required by the deserializer.
The serialization implementation of Regex
in JSON.net is too long to be listed here. You can click this link to read those 100+ lines code. For PowerJSON, this case is still simple.
To serialize the Regex
class, we can create a model class RegexInfo
which contains the pattern and options fields, and convert the Regex
class to that model. Since the model has default constructor and all needed fields, it can be handled by the JSON serializer and deserializer. Here's the implemetation for the Regex
converter.
class RegexConverter : JsonConverter<Regex, RegexConverter.RegexInfo>
{
protected override RegexInfo Convert (string fieldName, Regex fieldValue) {
return new RegexInfo () { Pattern = fieldValue.ToString (), Options = fieldValue.Options };
}
protected override Regex Revert (string fieldName, RegexInfo fieldValue) {
return new Regex (fieldValue.Pattern, fieldValue.Options);
}
[JsonSerializable]
internal struct RegexInfo
{
public string Pattern;
public RegexOptions Options;
}
}
The above 16 lines (including blank lines) make the Regex
JSON serializer about ready to be used. The last thing to do is assigning the converter to Regex
as the following line shows. Afterwards, all Regex
instances can be serialized and deserialized. The implementation is much more natural and simplier than JSON.net or the original fastJSON, which requires you to write raw JSON strings and read them back.
JSON.Manager.OverrideConverter<Regex> (new RegexConverter ());
Tips:
fastJSON.BonusPack.Converters
in PowerJSON has various converters to be used.
PowerJSON enables you to conditionally serialize or deserialize specific members according to external situations at run time. It is can be done with the IJsonInterceptor
interface and JsonInterceptor<T>
class, along with the JsonInterceptorAttribute
.
The serialization of an object has three phases which can be interception points:
- The serialization engine gets the object to be serialized.
- The serialization engine looks into the structure of the object, and loop through each field or property to serialize them.
- The serialization engine finishes the serialization.
The interceptor will allow you to take control in the above three phases, to determine the following five issues:
- Should a specific object be serialized.
- Should a member in the object be serialized.
- Should the name or value of the serialized member be changed before it is serialized.
- Should any information be appended with the serialized object.
- Should any further actions be taken after the serialization is done.
With the interceptor, we can...
- Determine whether an object or its specific members should be serialized or deserialized.
- Alter values during serialization and deserialization.
- Trace and debug serialization and deserialization.
- Write extra fields and values to the serialized results.
The following part will show how to use the interceptor.
Given a class like the following code.
[JsonInterceptor (typeof (TestInterceptor))]
public class InterceptorTestSample
{
public int Value;
public string Text;
public bool Toggle;
public string HideWhenToggleTrue = "Show when toggle false";
public DateTime Timestamp;
}
When we do serialization, we want the Timestamp
field to show the time when the serialization occurs. The HideWhenToggleTrue
field to be hidden when the Toggle
field is true
.
To accomplish the above requirement, we can use the JsonInterceptor<T>
class. We firstly annotate the InterceptorTestSample
class with the [JsonInterceptor (typeof (TestInterceptor))]
attribute.
After that, we implement the IJsonInterceptor
interface by overriding the JsonInterceptor<InterceptorTestSample>
class. The bold text in the code will fulfill the above requirements.
class TestInterceptor : JsonInterceptor<InterceptorTestSample> {
public override bool OnSerializing (InterceptorTestSample obj) {
obj.Value = 1;
Console.WriteLine ("serializing.");
return true;
}
public override void OnSerialized (InterceptorTestSample obj) {
obj.Value = 2;
Console.WriteLine ("serialized.");
}
public override bool OnSerializing (InterceptorTestSample obj, JsonItem item) {
Console.WriteLine ("serializing " + item.Name);
if (item.Name == "Text") {
obj.Timestamp = DateTime.Now;
item.Value = "Changed at " + obj.Timestamp.ToString ();
}
else if (item.Name == "HideWhenToggleTrue" && obj.Toggle) {
return false;
}
return true;
}
public override void OnDeserializing (InterceptorTestSample obj) {
obj.Value = 3;
Console.WriteLine ("deserializing.");
}
public override void OnDeserialized (InterceptorTestSample obj) {
obj.Value = 4;
Console.WriteLine ("deserialized.");
}
public override bool OnDeserializing (InterceptorTestSample obj, JsonItem item) {
Console.WriteLine ("deserializing " + item.Name);
if (item.Name == "Text") {
item.Value = "1";
}
return true;
}
}
We use the following code to serialize and deserialize the value.
public void IntercepterTest () {
var d = new InterceptorTestSample ();
var s = JSON.ToJSON (d, _JP);
Console.WriteLine (s);
var o = JSON.ToObject<InterceptorTestSample> (s);
d.Toggle = true;
s = JSON.ToJSON (d, _JP);
Console.WriteLine (s);
}
And the output of the code is listed below:
serializing.
serializing Value
serializing Text
serializing Toggle
serializing HideWhenToggleTrue
serializing Timestamp
serialized.
{"Value":1,"Text":"Changed at 2015-5-11 14:37:09","Toggle":false,
"HideWhenToggleTrue":"Show when toggle false","Timestamp":"2015-05-11T14:37:09"}
deserializing.
deserializing Value
deserializing Text
deserializing Toggle
deserializing HideWhenToggleTrue
deserializing Timestamp
deserialized.
serializing.
serializing Value
serializing Text
serializing Toggle
serializing HideWhenToggleTrue
serializing Timestamp
serialized.
{"Value":1,"Text":"Changed at 2015-5-11 14:37:09","Toggle":true,"Timestamp":"2015-05-11T14:37:09"}
The above code shows how to apply the JsonInterceptor
to a class with the invasive mode.
Sometimes, we don't have the source code in our hands. The following example will try to conditionally serialize the System.Net.WebException
class. Quite obviously, we don't have the source code of the WebException
class. We have to use the SerializationManager
to control the serialization result of the class. This example also demonstrates how to filter unwanted fields from be serialized.
Firstly, we create the interceptor class. It will order the serialization engine to append two extra name-value pairs to the serialization result, in the SerializeExtraValues
method. It also filters the properties to be serialized. In this example, only Status
and Message
properties are serialized.
public class WebExceptionJsonInterceptor : JsonInterceptor<System.Net.WebException>
{
public override IEnumerable<JsonItem> SerializeExtraValues (System.Net.WebException obj) {
return new JsonItem[] {
new JsonItem ("exceptionTime", DateTime.Now),
new JsonItem ("machine", Environment.MachineName)
};
}
public override bool OnSerializing (System.Net.WebException obj, JsonItem item) {
switch (item.Name) {
case "Status":
case "Message":
return true;
default:
return false;
}
}
}
Secondly, we call the OverrideInterceptor
method in the SerializationManager
to apply the interceptor to the class.
JSON.Manager.OverrideInterceptor<System.Net.WebException> (new WebExceptionJsonInterceptor ());
The following code will give some output:
try {
var c = System.Net.WebRequest.Create ("http://inexistent-domain.com");
using (var r = c.GetResponse ()) {
}
}
catch (System.Net.WebException ex) {
string s = JSON.ToJSON (ex, p);
Console.WriteLine (s);
}
The output of the code is listed below (lines wrapped for clarity):
{"httpstatus":"NameResolutionFailure",
"message":"Unable to resolve the host name: 'inexistent-domain.com'",
"exceptionTime":"2015-05-11T06:55:08Z",
"machine":"WMJ"}
Although many functions have been added to the original fastJSON, however, the performance is never degraded, but improved after many heavy refactors. According to the consoletest benchmark utility supplied in both projects, PowerJSON runs about 5% to 20% faster than the original fastJSON, which is already fast enough.
- First public release on CodeProject: 2015-4-1
- Updated source code file, introduced the new
SerializeStaticMembers
setting, added description about enum
value serialization with JsonConverter<>
: 2015-4-3 - Breaking change: All extensive attributes are named after
Json
. Updated source code file, added more description about the upgraded JsonConverter<>
: 2015-4-14 - Version 2.3.3: Rewrite of this article to reflect the latest change of PowerJSON. A documentation file and compiled DLL files are also attached in the download. 2015-5-11
- Version 2.3.4:
IJsonConverter
can be applied to classes
and structs
. Performance enhancement. Breaking change: Dictionary<string, object>
in primitive types changed to IDictionary<string,object>
2015-5-22 - Version 2.3.5: Breaking Change:
Convert
and Revert
method in JsonConverter<,>
changed to be protected
to allow the serialized type to be internal
types. More customized IJsonConverter
s added. Marked Serliaze
and Deserialize
delegates to be obsolete since they can be superceded by IJsonConverter
. 2015-5-24 - Version 2.4: Makes private members possible to be serialized. Partially supports the
DataContractAttribute
. Breaking Change: Serializable
and Deserializable
property of MemberOverride
class is changed to nullable boolean values. 2015-7-17 - Version 2.5: Supports serializing types which implements
IEnumerable
. 2015-8-25