/**************************************************************************** ** ** 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 /////////////////////////////////// } }