/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt VS Tools.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml;
using QtVsTools.Core;
///
/// The classes in this namespace provide support to the serialization and deserialization of
/// .NET objects using the JavaScript Object Notation (JSON) format. The transformation of
/// objects to and from JSON data is based on the DataContractJsonSerializer class provided
/// by the .NET framework, as documented in the following page:
///
/// https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-serialize-and-deserialize-json-data
///
/// To support the deserialization of polymorphic types, the concept of deferred deserialization
/// is introduced: if a field is marked for deferred deserialization, the corresponding JSON data
/// is not interpreted right way, but is rather stored for later processing, e.g. when the actual
/// type of the field can be determined.
///
///
namespace QtVsTools.Json
{
///
/// Public interface of objects representing JSON serialized data
///
///
public interface IJsonData : IDisposable
{
bool IsEmpty();
byte[] GetBytes();
}
///
/// Public interface of types providing deferred deserialization.
///
///
public interface IDeferredObject
{
object Object { get; }
bool HasData { get; }
void Deserialize();
}
///
/// Public interface of types containing deferred-deserialized objects
///
///
public interface IDeferredObjectContainer
{
void Add(IDeferredObject defObj);
IEnumerable PendingObjects { get; }
}
///
/// A Serializer object allows the serialization and deserialization of objects using the JSON
/// format, by extending the services provided by the DataContractJsonSerializer class.
///
///
class Serializer : Concurrent
{
private DataContractJsonSerializer serializer;
public static Serializer Create(Type type)
{
var _this = new Serializer();
return _this.Initialize(type) ? _this : null;
}
private Serializer()
{ }
private bool Initialize(Type type)
{
var settings = new DataContractJsonSerializerSettings();
if (settings == null)
return false;
settings.DataContractSurrogate = new DataContractSurrogate { Serializer = this };
settings.EmitTypeInformation = EmitTypeInformation.Never;
settings.UseSimpleDictionaryFormat = true;
serializer = new DataContractJsonSerializer(type, settings);
if (serializer == null)
return false;
return true;
}
public IJsonData Serialize(object obj)
{
var stream = new MemoryStream();
using (var writer = JsonReaderWriterFactory.CreateJsonWriter(stream)) {
try {
serializer.WriteObject(writer, obj);
writer.Close();
return new JsonData() { Stream = stream };
} catch (Exception exception) {
exception.Log();
if (stream != null && stream.CanRead && stream.Length > 0)
stream.Dispose();
return null;
}
}
}
public object Deserialize(IJsonData jsonData)
{
var data = jsonData as JsonData;
if (data == null)
return null;
if (data.XmlStream == null && !Parse(data))
return null;
lock (CriticalSection) {
try {
using (reader = XmlReader.Create(data.XmlStream)) {
var obj = serializer.ReadObject(reader, false);
if (obj is IDeferredObjectContainer container)
deferredObjects.ForEach(x => container.Add(x));
return obj;
}
} catch (Exception exception) {
exception.Log();
return null;
} finally {
reader = null;
deferredObjects.Clear();
data.XmlStream.Position = 0;
}
}
}
///
/// Parses raw JSON data and returns the corresponding IJsonData object.
///
/// Raw JSON data
/// IJsonData object corresponding to the data provided
///
public static IJsonData Parse(byte[] rawJsonData)
{
if (rawJsonData == null)
rawJsonData = new byte[0];
var data = new JsonData()
{
Stream = new MemoryStream(rawJsonData)
};
if (!Parse(data)) {
data.Dispose();
return null;
}
return data;
}
private static bool Parse(JsonData data)
{
try {
var q = new XmlDictionaryReaderQuotas();
using (var reader = JsonReaderWriterFactory.CreateJsonReader(data.Stream, q)) {
reader.Read();
var xmlData = Encoding.UTF8.GetBytes(reader.ReadOuterXml());
reader.Close();
data.XmlStream = new MemoryStream(xmlData);
}
return true;
} catch (Exception exception) {
exception.Log();
return false;
}
}
#region //////////////////// JsonData /////////////////////////////////////////////////////
private class JsonData : Disposable, IJsonData
{
public MemoryStream Stream { get; set; }
public MemoryStream XmlStream { get; set; }
byte[] IJsonData.GetBytes()
{
return Stream.ToArray();
}
bool IJsonData.IsEmpty()
{
return (Stream == null || !Stream.CanRead || Stream.Length == 0)
&& (XmlStream == null || !XmlStream.CanRead || XmlStream.Length == 0);
}
protected override void DisposeManaged()
{
if (Stream != null)
Stream.Dispose();
if (XmlStream != null)
XmlStream.Dispose();
}
}
#endregion //////////////////// JsonData //////////////////////////////////////////////////
#region //////////////////// Data Contract Surrogate //////////////////////////////////////
static readonly Exclusive sharedInstance = new Exclusive();
private XmlReader reader = null;
private readonly List deferredObjects = new List();
public static IJsonData GetCurrentJsonData()
{
Serializer _this = sharedInstance;
try {
var root = new StringBuilder();
root.Append("");
while (_this.reader.IsStartElement())
root.Append(_this.reader.ReadOuterXml());
root.Append("");
var xmlData = Encoding.UTF8.GetBytes(root.ToString());
return new JsonData { XmlStream = new MemoryStream(xmlData) };
} catch (Exception exception) {
exception.Log();
return null;
}
}
class DataContractSurrogate : IDataContractSurrogate
{
public Serializer Serializer { get; set; }
Type IDataContractSurrogate.GetDataContractType(Type type)
{
if (typeof(IDeferredObject).IsAssignableFrom(type)) {
// About to process a deferred object: lock shared serializer
sharedInstance.Set(Serializer);
}
return type;
}
object IDataContractSurrogate.GetDeserializedObject(object obj, Type targetType)
{
if (typeof(IDeferredObject).IsAssignableFrom(targetType)) {
// Deferred object deserialized: add to list of deferred objects...
Serializer.deferredObjects.Add(obj as IDeferredObject);
// ...and release shared serializer
sharedInstance.Release();
}
return obj;
}
object IDataContractSurrogate.GetObjectToSerialize(object obj, Type targetType)
{
if (obj is IDeferredObject) {
// Deferred object serialized: release shared serializer
sharedInstance.Release();
return (obj as IDeferredObject).Object;
}
return obj;
}
object IDataContractSurrogate.GetCustomDataToExport(
MemberInfo memberInfo,
Type dataContractType)
{ throw new NotImplementedException(); }
object IDataContractSurrogate.GetCustomDataToExport(
Type clrType,
Type dataContractType)
{ throw new NotImplementedException(); }
Type IDataContractSurrogate.GetReferencedTypeOnImport(
string typeName,
string typeNamespace,
object customData)
{ throw new NotImplementedException(); }
CodeTypeDeclaration IDataContractSurrogate.ProcessImportedType(
CodeTypeDeclaration typeDeclaration,
CodeCompileUnit compileUnit)
{ throw new NotImplementedException(); }
void IDataContractSurrogate.GetKnownCustomDataTypes(Collection customDataTypes)
{ throw new NotImplementedException(); }
}
#endregion //////////////////// Data Contract Surrogate ///////////////////////////////////
}
}