APJSON






4.67/5 (5 votes)
This is an alternative for "fastJSON"
- Download Sources v1.0
- Download Binaries v1.0 (with PDB)
- Download Apolyton FastJson Sources v0.93 - 745.9 KB
- Download Apolyton FastJson Binaries v0.93.zip - 241.3 KB
- Download Apolyton FastJson Sources v0.92 - 806.8 KB
- Download Apolyton FastJson Binaries v0.92.zip - 251 KB
- Download Apolyton FastJson Sources v0.92 - 806.8 KB
- Download Apolyton FastJson Binaries v0.9.zip - 323.3 KB
- Download Apolyton FastJson Sources v0.9 - 226.9 KB
Introduction
This is an alternative version of Gholam's great JSON library which brings a new deserializer, code and workflow optimizations and some new features at acceptable performance cost (if).
Key Features (v1.0)
- Event faster deserialization of a json string into generic values (
IJsonValue
) - Type extension support (custom type names)
- Custom type support (custom (de-)serializers)
- Silverlight 5 support
- Optionally checks for
DataMember
attribute. - Handles
DataMember.Name
representing the json field name - Handles
IgnoreDataMember
by ignoring properties decorated with it. - Handles date time kind through the
JsonDateTimeOptions
attribute. - Handles custom date formats through the
JsonDateTimeOptions
attribute. - [New] Supports
HashSet<T>
(with some performance penalty) - [New]
Json.Current.BuildUp
supports nowJsonArray
into collection types. - (Most) built-in value types are supported.
- Non-abstract reference types are supported (including inheritance)
- Custom type support.
- Enhanced debugging capabilities purposes see the
GetSerializationMembers
method - Committed to quality over performance.
==> See changelog at the bottom of this article.
==> See fastJSON 2.0.9 feature list for more complete list of features.
Using the code
Since ApJson is built on top of fastJSON, therefore it's API has some overlap:
Serialize an object
Apolyton.FastJson.Json.Current.ToJson(c);
Note that in all cases in which no explicit parameter object is provided, the default parameters are used.
Deserialize an object
Option 1: Straight forward
In order to deserialize an object directly (with custom type support), your call should like the following:
string jsonText = "{...}"; // your json string var myObject = (MyClass)Apolyton.FastJson.Json.Current.ReadObject(jsonText);
Note 1: type extensions must be enabled and present in the jsin string for this feature to work (otherwise the deserializer cannot determine, which kind of object to create.
Note 2: there is also a generic version of that method
Option 2: Sniff'n go
However, A-FastJson comes with another deserializer, the JsonValueDeserializer
which returns a value store based on IJsonValue
. This operation is a lot faster than the ReadObject
method shown above, but is not that strongly typed. Reading into a json value can be accomplished by
string jsonText = "{...}"; // your json string JsonObject myObject = Apolyton.FastJson.Json.Current.ReadJsonValue(jsonText);
The JsonObject
class is essentially a dictionary which allows you to sniff into its values before continuing the deserialization process. This is for instance useful for protocol validation since it allows you to trash the json request before full deserialization has happened which saves time and increases potentially your i/o (ReadJsonValue
is almost twice as fast than ReadObject
, see below).
An instance of JsonObject
can be used to populate an existing instance of your CLR objects. For that, use the BuilUp
method on the Json
singleton:
string jsonText = "{...}"; // your json string var deserializedStore = (JsonObject)Apolyton.FastJson.Json.Current.ReadJsonValue(jsonText); var target = new MyClass(); Apolyton.FastJson.Json.Current.BuildUp(target, deserializedStore);
For optimal performance, you should pool your target objects; The BuildUp
method is supposed to ensure that you can recycle your instances.
Configuration
By default, the Json
class instance uses the default parameters for serialization and deserialization. Due to internal mechanisms, these configuration objects are expensive in contrast to those in fastJSON. This is because all serialization meta-information is attached to it.
The parameter names should be self explicit and are explained briefly in code. Since v0.93, there is a clear separation of parameters which refer to serialization and deserialization. Properties on the JsonParameters class refer to both operations. UseGlobalTypes is obsolete.
Inspecting the configuration
For debugging or automated protocol checks, one may want to see the list of properties which are visible
to the JSON (de-)serializer. For that, you can use the GetSerializationMembers
method.
// For the default parameter Apolyton.FastJson.Json.Current.GetSerializationMembers(typeof(MyClass)); or // For any parameter object new JsomParameter().GetSerializationMembers(typeof(MyClass);
Custom Type Support
The custom type support of FastJSON has been extended and reviewed in order to be platform independent. In essence, a parameter object can register a serialization and deserialization handler which are plain delegates. This can happen for instance through:
// Preamble for illustration purposes only JsonParameters parameters = CreateTestParameters(); JsonSerializer serializer = new JsonSerializer(parameters); CustomTypeClass customTypeClass = new CustomTypeClass() { Custom = new CustomTypeClass.CustomType() }; // Register two delegates, we serialize to 'yes', we deserialize to 'null' parameters.RegisterCustomType(typeof(CustomTypeClass.CustomType), (obj) => { return "yes"; }, (obj) => { return null; }); String jsonString = serializer.Serialize(customTypeClass);
That will render to a JSON string like:
{"Custom":"yes"}
The submitted inline-delegate is called for each instance of the given type. Note that you cannot register a custom type handler for internal types. If you try so, an ArgumentException
is thrown by the register method.
For more maintainable code, one can also implement the ICustomTypeSerializer
interface and register that instance as a custom type handler:
There is already a base class, namely CustomTypeSerializer
, which pre-implements that interface and lets us focus on the important part of our code (and not the formal one):
internal class ObjectIdSerializer : CustomTypeSerializer { /// <summary> /// Gets the type for which this serializer is responsible for. /// </summary> public override Type Type { get { return typeof(ObjectId); } } /// <summary> /// Returns true: yes, this class can deserialize ObjectId. /// </summary> public override bool CanDeserialize { get{return true; } } /// <summary> /// Returns true: yes, this class can serialize ObjectId. /// </summary> public override bool CanSerialize { get { return true; } } /// <summary> /// Returns the string of the object id. /// </summary> /// <param name="data"></param> /// <returns></returns> public override string Serialize(object data) { return data.ToString(); } /// <summary> /// Returns the object id representing the string. /// </summary> public override object Deserialize(string jsonString) { if (!String.IsNullOrEmpty(jsonString)) { return new ObjectId(jsonString); } else { return ObjectId.Empty; } } }
That example serializes Mongo ObjectId
structs into strings and the way back. Note that we didn't implement TypeName
which can define the name of the type (see Type Descriptors and Polymorphism); returning null leads to default behavior (recommended).
Type Descriptors and Polymorphism
This chapter is mostly relevant for advanced deserialization scenarios. Serializing objects works normally without need of interaction or configuration. The limits of configuration-free deserialization is reached, when polymorphic objects are used.
Understanding the problem
A simple scenario involving polymorph objects is a list of animals in which each item can be some concrete sort of animal, like dog or cat. Without type descriptors, the deserializer would deserialize each item into an animal. So given a list of 3 animals, 2 dogs and one cat, the serializer would do its job properly by writing a json array with 3 items and their properties within:
[ { "name"="Brutus", barkLevel="high", "power"="medium" }, { "name"="Bronto", barkLevel="high", "power"="high"}, { "name"="Silvester", "intelligence"="low", "creativity"="high", "luck"="not-existent" } ]
However, the deserializer just sees a list of 3 animals. Given the text information above, the type of line is lost. Therefore, the deserliazed list would only contain a list with 3 Animals ignoring all additional information in the stream. A solution to this problem is to serialize the type information as well, normally into the special property named $type
:
[ { "$type"="dog", "name"="Brutus", barkLevel="high", "power"="medium" }, { "$type"="dog", "name"="Bronto", barkLevel="high", "power"="high"}, { "$type"="cat", "name"="Silvester", "intelligence"="low", "creativity"="high", "luck"="not-existent" } ]
The value of the type field can be controlled through type descriptors:
Type Descriptors
The key responsibility of type descriptors is to describe a type by a string. By default, FastJson and ApJson describe a given type by the assembly qualified name which is an awkward long string:
For System.Object: "System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
The good about this string is, that within the .NET world it should work nicely, the bad thing about these type names are their portability and length. Obviously, the question 'What is a good type name for a user type?' cannot be answered by this toolkit. Although the recommendation is to use the DataContractTypeDescriptor
, the type description can be custom by implementing an own sub-class of JsonTypeDescriptor
and register it to the parameters:
As mentioned above, the JsonTypeDescriptor
generates assembly qualified type names. It is easy to imagine (and implement) type descriptors which use the FullName
of a type or just the Name
. However, the recommended way is to use the DataContractTypeDescriptor
:
As its name indicates, the descriptor is using the DataContractAttribute
to determine the name of the type. As a fallback mechanism, the FullName
of the type is used (if the given type does not have a DataContract
attribute on it or it has no Name
assigned). Assigning a name to one of our classes is straight forward:
[System.Runtime.Serialization.DataContract(Name="dog")] public class Dog { ... }
Optionally, we also might want to provide a namespace, but the example above will already lead to a readable output which is illustrated above.
Points of Interest
- Always reuse configuration (
JsonParameter
) objects. Performance drops dramatically, if you don't. - The
IJsonValue
concept is inspired from the JSON API in Silverlight done by Microsoft. I found it very useful to sniff into incoming requests and trash them when they are not following basic expectations (mandatory fields missing). Running a full deserialization in order to do the samewas appearing to me as a waste of resources. - The code is covered by unit tests (around 160+) which should ensure high quality of each release.
- Byte enumeration is not supported. Use
byte[]
instead. DataMember.OrderNumber
is ignoredJsonDateTimeOptions.Format
follows the specification of DateTime.ParseExact.- Avoid using
internal
properties or members in (de-) serialization. The framework reacts partially quite strangely when those properties are accessed through reflection -and in most cases, there is a negative performance impact (that's a .NET thing). Silverlight will for instance throw access violation or method not found exceptions for internal types.
Benchmarks
Before crunching some numbers, some points should be enlightened since they are easily forgotten:
- All benchmarks published by framework developers, like this or fastJSON are optimized results. Real life results can look very differently. This is not bad intention, but perfectly normal. Therefore:
- All results published here just performance indicators. You should compare the framework's performance in your end-to-end scenario while considering that:
- Performance varies not only from run-to-run, but also from class-to-class and the data in it.
- All benchmarks are run against fastJSON 2.0.13 which was the reference at publish time.
- When reading benchmark results ensure that input and output are the same (*1)
(*1) For instance, the fastJSON benchmark compares with BinaryFormatter which takes a stream as input. fastJSON cannot process streams. This aspect is ignored, but it leads to a potentially wrong assumption that fastJSON is faster than BinaryFormatter..
Scenario 1: A-FastJSON vs fastJSON x86 (custom types)
- (A)-FastJSON serialization is usually 20% faster than fastJSON.
- (A1)-FastJSON deserialization to
IJsonValue
is faster than serialization and ~300% faster fastJSON. - (A2)-FastJSON deserialization to
IJsonValue
, then build up of into given class is almost ~20% faster than fastJSON (+10% compared to v0.91) - (A3)-FastJSON deserializatio9n
to
IJsonValue
, then build up of into given class with type extensions, custom type name (Data Contract support) is ~9% faster than FastJson. - (A4)-FastJSON deserialization into object is ~10% faster or equal to fastJSON
- (A5)-FastJSON deserialization into a known object is ~20% faster than fastJSON (
ReadObject<T>
)
Scenario 2: A-FastJSON vs fastJSON x86 (custom types & exotic types)
- (A)-FastJSON serialization is usually 15% faster than fastJSON.
- (A1)-FastJSON deserialization to
IJsonValue
is faster than serialization and ~300% faster fastJSON. - (A2)-FastJSON deserialization to
IJsonValue
, then build up of into given class is almost ~20% faster than fastJSON (+10% compared to v0.91) - (A3)-FastJSON deserializatio9n
to
IJsonValue
, then build up of into given class with type extensions, custom type name (Data Contract support) is slower than FastJson. - (A4)-FastJSON deserialization into object is ~19% faster or equal to fastJSON
- (A5)-FastJSON deserialization into a known object is ~15% faster than fastJSON (
ReadObject<T>
)
Note that 64 Bit scenarios are not listed anymore since they don't reveal surprising measures (and are therefore not meaningful).
Conclusions
- Both, ApJson and fastJSON are reasonably fast.
- In 64 bit scenarios the advantage of ApJson is lower.
- If one needs built in data set/ data table support, fastJSON is likely the better option.
- If one needs exotic type support (dictionaries, hash-sets etc), ApJson is likely the better option.
- The
IJsonValue
conversion is surprisingly fast and seems to be a very good option -especially, if one considers that the second step, converting the generic dictionary into a given type, is optional. - Due to code optimizations, ApJson is meanwhile faster than FastJson in most of the scenarios it defines.
Known Issues and Limitations
DataSet
andDataTable
are not supported by theBuildUp
method (*2). Anyway, the value to convert from one generic type into another one should be quite low. Given enough interest, extension methods might be added onIJsonValue
.HashSet
in deserialization is not supported (lack of interface to populate the hash set collection).DataMember
withName = "$type"
and comparable is still allowed and leads potentially to wrong behavior.InvalidProgramException
is thrown when property indexer (akathis[]
) is defined on class to serialize- Deserialization into non-public types fails with
TypeAccessException
. UseGlobalTypes
doesn't work withJsonValueDeserializer
.
(*2) DataTable and DataSet can be deserialized by the
ReadDataTable
/ ReadDataSet
methods, however this part of the code is currently not tested.
Release Notes:
v1.0:
- Small performance improvements using JsonValue deserialization.
- [NEW] Can now deserialize into a HashSet<>
- [NEW] Allowing to buildup json arrays.
- [NEW] Deserialization into enumeration types is now trying to set an array of the items (instead of exception)
- [FIX] Registry did not always respect property read/ write specifiers
- [FIX] Crash on deserializing into non-generic Array.
- [FIX] JsonPropertyInfo.Copy copied too not relevant values for Silverlight.
- [FIX] Silverlight property/ field getter for certain types resulted in NullReferenceExceptions
- [CHANGE]
JsonParameter.UseGlobalTypes
has been removed. - Other fixes..
v.93 Release Candidate
- Small performance improvement 5%-10%.
- [Cancelled] (Cannot declare explicit cast operators on interfaces)
IJsonValue.
- [New] Explicit cast operators on
JsonPrimitive
class - [New]
JsonParameter
is reviewed. The old version misses clear separation of what is serialization option, what is deserialization and what is common. - [New] Custom date format support through
'JsonDateTimeOptions.Format
' format string (note that automatic conversions are supported). - [Fix]
JsonPrimitive
decimal was using integer parsing. - [Fix]
JsonPrimitive
ToChar was not throwing an exception when local value had more than one character - [Fix]
IJsonValue
implementations now throw the promisedNotSupportedException
instead ofInvalidOperationException
. - [Fix]
BuildUp
builtDictionary<,>
incorrectly. - [Change] Setting default
UseExtensions
to false since it is not required in most of the cases. - [Change]
JsonValue
builder will become the default deserializer except for data table and data set objects. - [Change]
SerializationPolicy
is obsolete and to be replaced withMemberStrategy
. - [Change] Visibility scope of
JsonObject
,JsonValue
,JsonArray
corrected down tointernal
(they are read-only objects). - [Change]
DateTimeKind
specifications (local, Utc, etc) have only an implicit effect on deserialized values, if the string values is zulu time. Otherwise, the kind is set according to the option, but the value is not modified. - [Change] Type Extensions are now disabled by default as this is an advanced case and has a significant performance impact.
v0.92
First intermediate release before API stability is guaranteed (in v1.0)
- Removing support of
xmlignore
attribute, it is replaced byDataMember
,IgnoreDataMember
- New:
JsonPrimitive
custom type support. - New: Silverlight 5 support.
- New: A
SerializationException
is thrown when an attempt is done to serialize a class without (visible) properties. Previously, this was rendering to '{}' which will lead to problems on receivers side. - New:
Json.ToJsonBytes
returns a byte array representing the json string bytes in the configured encoding. - New: A date time property can be decorated with the
DateTimeOptions
attribute which defines the expected kind of date time (utc or not). The deserializer will automatically convert, if appropriate. - Change: Thread static attribute has been removed from JSON class. It was causing irritations, since each thread had its own default parameters. Any change applied to the parameters needed to be re-set for each worker thread.
- Fix: Can register custom type handler for non default JSON parameters.
- Fix:
DateTime
deserialization was always converting to local time. Now the field can declare theJsonDateTimeOptions
attribute which allows to specify the desired value. - Fix: If last property is null or empty and
SerializeNullValues
is true, a final ',' was rendered anyway (ie. { i:1,} where 'j' is nullable and null). - Increasing unit test depth on
JsonRegistry
and other related classes. - Improved error reporting when duplicate data member is detected.
v0.9 Fork from fastJSON 2.0.9
Serialization
DateTime
to UTC is now respecting the kind of the date time (JsonSerializer_DateTimeUtc
)- Byte
array was not proper when a List of bytes was serialized (see
JsonSerializer_ByteEnumeration
) - Serialization of custom types implementing
IList
was not considered TimeSpan
was wrongly serialized.
Deserialization
- Deserialization of a number of bytes was failing
- Wrong date time was returned when given string was in UTC and parameter was set to avoid UTC dates
- Changing serialization/ deserialization values at runtime could lead to unexpected output.
- Code was changing some properties on its own behalf.
MyPropInfo
CanWrite
was always false for fieldsFill
was never false and unused. Removed.GenericTypes
was always null except for dictionaries with the name 'Dictionary'.
Benchmark
- Fixing time measure flaw in benchmark tool.
History
28th August 2013: Release of v1.0 (after long testing period).
30th January 2013: Release of v0.93
4th January 2013: Release of v.92
26th November 2012: Release of v.90
25th October 2012: Fork of FastJSON. Development started.