// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 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(); string GetString(); } /// /// 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. /// /// public class Serializer : Concurrent { private DataContractJsonSerializer serializer; public static Serializer Create(Type type) { var obj = new Serializer(); return obj.Initialize(type) ? obj : null; } private Serializer() { } private bool Initialize(Type type) { var settings = new DataContractJsonSerializerSettings { DataContractSurrogate = new DataContractSurrogate { Serializer = this }, EmitTypeInformation = EmitTypeInformation.Never, UseSimpleDictionaryFormat = true }; serializer = new DataContractJsonSerializer(type, settings); return serializer != null; } public IJsonData Serialize(object obj, bool indent = false) { var stream = new MemoryStream(); using var writer = JsonReaderWriterFactory .CreateJsonWriter(stream, Encoding.UTF8, true, indent); try { serializer.WriteObject(writer, obj); writer.Close(); return new JsonData { Stream = stream }; } catch (Exception exception) { exception.Log(); if (stream is { CanRead: true, Length: > 0 }) stream.Dispose(); return null; } } public object Deserialize(IJsonData jsonData) { if (jsonData is not JsonData data) return null; if (data.XmlStream == null && !Parse(data)) return null; if (data.XmlStream == null) 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) { rawJsonData ??= Array.Empty(); 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(); } string IJsonData.GetString() { return Encoding.UTF8.GetString(((IJsonData)this).GetBytes()); } bool IJsonData.IsEmpty() { return Stream is not { CanRead: true, Length: not 0 } && XmlStream is not { CanRead: true, Length: not 0 }; } protected override void DisposeManaged() { Stream?.Dispose(); XmlStream?.Dispose(); } } #endregion //////////////////// JsonData ////////////////////////////////////////////////// #region //////////////////// Data Contract Surrogate ////////////////////////////////////// private static readonly Exclusive SharedInstance = new(); private XmlReader reader; private readonly List deferredObjects = new(); public static IJsonData GetCurrentJsonData() { Serializer instance = SharedInstance; try { var root = new StringBuilder(); root.Append(""); while (instance.reader.IsStartElement()) root.Append(instance.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; } } private 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 deferredObject) { // Deferred object serialized: release shared serializer SharedInstance.Release(); return deferredObject.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 /////////////////////////////////// } }