Browse Source

Extras property *Breaking Change*

Replaced JsonContent in favour of the new System.Text.Json.Nodes.JsonNode
vpenades 2 years ago
parent
commit
02020ed3b7

+ 76 - 1
src/Shared/_Extensions.cs

@@ -5,6 +5,8 @@ using System.Numerics;
 using System.Linq;
 using System.Linq;
 
 
 using SharpGLTF.Schema2;
 using SharpGLTF.Schema2;
+using System.Text.Json;
+using System.Diagnostics.CodeAnalysis;
 
 
 namespace SharpGLTF
 namespace SharpGLTF
 {
 {
@@ -672,7 +674,7 @@ namespace SharpGLTF
         #endregion
         #endregion
 
 
         #region serialization
         #region serialization
-
+        
         public static Byte[] ToUnderlayingArray(this ArraySegment<Byte> segment)
         public static Byte[] ToUnderlayingArray(this ArraySegment<Byte> segment)
         {
         {
             if (segment.Offset == 0 && segment.Count == segment.Array.Length) return segment.Array;
             if (segment.Offset == 0 && segment.Count == segment.Array.Length) return segment.Array;
@@ -740,5 +742,78 @@ namespace SharpGLTF
         }
         }
 
 
         #endregion
         #endregion
+
+        #region json
+
+        // note: these methods have been added to newer versions of json, so they might be removed eventually        
+
+#if NET6_0_OR_GREATER
+        [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.Json.Nodes.JsonValue))]
+        [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.Json.Nodes.JsonArray))]
+        [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.Json.Nodes.JsonObject))]
+#endif
+        public static System.Text.Json.Nodes.JsonNode DeepClone(this System.Text.Json.Nodes.JsonNode node)
+        {
+            // issue tracking both DeepClone and DeepEquals: https://github.com/dotnet/runtime/issues/56592            
+
+            if (node == null) return null;
+
+            System.Text.Json.Nodes.JsonNode clone = null;            
+
+            #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
+            if (node is System.Text.Json.Nodes.JsonValue asValue) clone = asValue.Deserialize<System.Text.Json.Nodes.JsonValue>();
+            if (node is System.Text.Json.Nodes.JsonArray asArray) clone = asArray.Deserialize<System.Text.Json.Nodes.JsonArray>();
+            if (node is System.Text.Json.Nodes.JsonObject asObject) clone = asObject.Deserialize<System.Text.Json.Nodes.JsonObject>();
+            #pragma warning enable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
+
+            if (clone == null) throw new NotImplementedException();
+
+            System.Diagnostics.Debug.Assert(node.DeepEquals(clone, 0.0001));
+
+            return clone;
+        }
+
+        public static bool DeepEquals(this System.Text.Json.Nodes.JsonNode x, System.Text.Json.Nodes.JsonNode y, double precission)
+        {
+            if (x == y) return true;
+            if (x == null) return false;
+            if (y == null) return false;
+
+            if (x is System.Text.Json.Nodes.JsonValue xval && y is System.Text.Json.Nodes.JsonValue yval)
+            {
+                if (xval.TryGetValue<double>(out var xfl) && yval.TryGetValue<double>(out var yfl))
+                {
+                    return Math.Abs(xfl-yfl) <= precission;
+                }
+
+                return xval.ToJsonString() == yval.ToJsonString();
+            }
+
+            if (x is System.Text.Json.Nodes.JsonArray xarr && y is System.Text.Json.Nodes.JsonArray yarr)
+            {
+                if (xarr.Count != yarr.Count) return false;
+                for (int i = 0; i < xarr.Count; ++i)
+                {
+                    if (!xarr[i].DeepEquals(yarr[i], precission)) return false;
+                }
+                return true;
+            }
+
+            if (x is System.Text.Json.Nodes.JsonObject xobj && y is System.Text.Json.Nodes.JsonObject yobj)
+            {
+                if (xobj.Count != yobj.Count) return false;
+
+                foreach (var xkvp in xobj)
+                {
+                    if (!yobj.TryGetPropertyValue(xkvp.Key, out var yvvv)) return false;
+                    if (!xkvp.Value.DeepEquals(yvvv, precission)) return false;
+                }
+                return true;
+            }
+
+            return false;
+        }
+
+        #endregion
     }
     }
 }
 }

+ 0 - 668
src/SharpGLTF.Core/IO/JsonContent.Impl.cs

@@ -1,668 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Json;
-
-using CULTURE = System.Globalization.CultureInfo;
-using JSONELEMENT = System.Text.Json.JsonElement;
-using JSONOPTIONS = System.Text.Json.JsonSerializerOptions;
-using JSONPROPERTY = System.Collections.Generic.KeyValuePair<string, object>;
-
-namespace SharpGLTF.IO
-{
-    interface IJsonCollection : ICloneable
-    {
-        bool IsArray { get; }
-        bool IsObject { get; }
-        int Count { get; }
-    }
-
-    static class _JsonStaticUtils
-    {
-        #region serialization
-
-        #if NET6_0_OR_GREATER
-        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(JsonContent._JsonTrimmingError1)]
-        #endif
-        public static string ToJson(Object obj, JSONOPTIONS options)
-        {
-            if (obj == null) return String.Empty;
-
-            if (options == null)
-            {
-                options = new JSONOPTIONS
-                {
-                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
-                    DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
-                    WriteIndented = true
-                };
-            }
-
-            #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
-            return JsonSerializer.Serialize(obj, obj.GetType(), options);
-            #pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
-        }
-
-        /// <summary>
-        /// Serializes data trees into trees of <see cref="IConvertible"/>, <see cref="_JsonArray"/> and <see cref="_JsonObject"/>.
-        /// </summary>
-        /// <param name="value">Any <see cref="IConvertible"/> array, list, or dictionary.</param>
-        /// <returns>An <see cref="IConvertible"/>, <see cref="_JsonArray"/> or <see cref="_JsonObject"/>.</returns>
-        public static Object Serialize(Object value)
-        {
-            if (value == null) throw new ArgumentNullException(nameof(value));
-            if (value is IConvertible cvalue && IsJsonSerializable(cvalue)) return cvalue;
-            if (_JsonObject.TryCreate(value, out _JsonObject dict)) return dict;
-            if (_JsonArray.TryCreate(value, out _JsonArray array)) return array;
-
-            throw new ArgumentException($"Can't serialize {value.GetType().Name}", nameof(value));
-        }
-
-        #if NET6_0_OR_GREATER
-        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(JsonContent._JsonTrimmingError1)]
-        #endif
-        public static Object Deserialize(Object obj, Type type, JSONOPTIONS options = null)
-        {
-            if (options == null)
-            {
-                options = new JSONOPTIONS
-                {
-                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
-                    DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
-                    WriteIndented = true
-                };
-            }
-
-            var json = ToJson(obj, options);
-
-            #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
-            return JsonSerializer.Deserialize(json, type, options);
-            #pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
-        }
-
-        public static Object Deserialize(JSONELEMENT element)
-        {
-            if (element.ValueKind == JsonValueKind.Null) return null;
-            if (element.ValueKind == JsonValueKind.False) return false;
-            if (element.ValueKind == JsonValueKind.True) return true;
-            if (element.ValueKind == JsonValueKind.String) return element.GetString();
-            if (element.ValueKind == JsonValueKind.Number) return element.GetDouble();
-            if (element.ValueKind == JsonValueKind.Array) return _JsonArray.CreateFrom(element);
-            if (element.ValueKind == JsonValueKind.Object) return _JsonObject.CreateFrom(element);
-
-            throw new NotImplementedException();
-        }
-
-        public static Object GetNode(Object current, params IConvertible[] path)
-        {
-            foreach (var part in path)
-            {
-                if (part is int index && current is IReadOnlyList<Object> array) { current = array[index]; continue; }
-                if (part is string key && current is IReadOnlyDictionary<String, Object> dict) { current = dict[key]; continue; }
-                throw new ArgumentException("Invalid path", nameof(path));
-            }
-
-            return current;
-        }
-
-        public static T GetValue<T>(Object current, params IConvertible[] path)
-            where T : IConvertible
-        {
-            current = GetNode(current, path);
-
-            if (current is IConvertible value)
-            {
-                return (T)Convert.ChangeType(value, typeof(T), CULTURE.InvariantCulture);
-            }
-
-            throw new ArgumentException("Invalid path", nameof(path));
-        }
-
-        public static bool IsJsonSerializable(Object value) { return IsJsonSerializable(value, out _); }
-
-        public static bool IsJsonSerializable(Object value, out Object invalidValue)
-        {
-            invalidValue = null;
-
-            if (value == null) return false;
-
-            if (value is IConvertible cvt) return IsJsonSerializable(cvt);
-
-            if (value is IDictionary dict)
-            {
-                if (dict.Count == 0) { invalidValue = value; return false; }
-
-                foreach (DictionaryEntry entry in dict)
-                {
-                    if (!IsJsonSerializable(entry.Value, out invalidValue)) return false;
-                }
-
-                return true;
-            }
-
-            if (value is IReadOnlyDictionary<string, object> dictXY)
-            {
-                if (dictXY.Count == 0) { invalidValue = value; return false; }
-
-                foreach (var item in dictXY.Values)
-                {
-                    if (!IsJsonSerializable(item, out invalidValue)) return false;
-                }
-
-                return true;
-            }
-
-            if (value is IEnumerable array)
-            {
-                if (!array.Cast<Object>().Any()) { invalidValue = value; return false; }
-
-                foreach (var item in array)
-                {
-                    if (!IsJsonSerializable(item, out invalidValue)) return false;
-                }
-
-                return true;
-            }
-
-            invalidValue = value;
-
-            return false;
-        }
-
-        private static bool IsJsonSerializable(IConvertible cvt)
-        {
-            switch (cvt.GetTypeCode())
-            {
-                case TypeCode.Empty:
-                case TypeCode.DBNull:
-                case TypeCode.Object:
-                case TypeCode.DateTime:
-                    return false;
-            }
-
-            if (cvt is Single scvt) return !Single.IsNaN(scvt) && !Single.IsInfinity(scvt);
-            if (cvt is Double dcvt) return !Double.IsNaN(dcvt) && !Double.IsInfinity(dcvt);
-
-            return true;
-        }
-
-        #endregion
-
-        #region equality
-
-        public static bool AreEqualByContent(Object x, Object y, float precission = 0)
-        {
-            #pragma warning disable IDE0041 // Use 'is null' check
-            if (Object.ReferenceEquals(x, y)) return true;
-            if (Object.ReferenceEquals(x, null)) return false;
-            if (Object.ReferenceEquals(y, null)) return false;
-            #pragma warning restore IDE0041 // Use 'is null' check
-
-            if (x is IConvertible xval && y is IConvertible yval)
-            {
-                return AreValuesEqual(xval, yval, precission);
-            }
-
-            if (x is IReadOnlyList<object> xarr && y is IReadOnlyList<object> yarr)
-            {
-                return _JsonArray.AreEqualByContent(xarr, yarr, precission);
-            }
-
-            if (x is IReadOnlyDictionary<string, object> xdic && y is IReadOnlyDictionary<string, object> ydic)
-            {
-                return _JsonObject.AreEqualByContent(xdic, ydic, precission);
-            }
-
-            bool isValidType(Object z)
-            {
-                if (z is IConvertible) return true;
-                if (z is IReadOnlyList<Object>) return true;
-                if (z is IReadOnlyDictionary<string, object>) return true;
-                return false;
-            }
-
-            if (!isValidType(x)) throw new ArgumentException($"Invalid type: {x.GetType()}", nameof(x));
-            if (!isValidType(y)) throw new ArgumentException($"Invalid type: {y.GetType()}", nameof(y));
-
-            return false;
-        }
-
-        private static bool AreValuesEqual(IConvertible x, IConvertible y, float precission = 0)
-        {
-            var xc = x.GetTypeCode();
-            var yc = y.GetTypeCode();
-
-            if (precission > 0)
-            {
-                if (xc == TypeCode.Decimal || yc == TypeCode.Decimal)
-                {
-                    var xf = y.ToDecimal(CULTURE.InvariantCulture);
-                    var yf = y.ToDecimal(CULTURE.InvariantCulture);
-                    return Math.Abs((double)(xf - yf)) <= precission;
-                }
-
-                if (xc == TypeCode.Double || yc == TypeCode.Double)
-                {
-                    var xf = y.ToDouble(CULTURE.InvariantCulture);
-                    var yf = y.ToDouble(CULTURE.InvariantCulture);
-                    return Math.Abs(xf - yf) <= precission;
-                }
-
-                if (xc == TypeCode.Single || yc == TypeCode.Single)
-                {
-                    var xf = y.ToSingle(CULTURE.InvariantCulture);
-                    var yf = y.ToSingle(CULTURE.InvariantCulture);
-                    return Math.Abs(xf - yf) <= precission;
-                }
-            }
-
-            if (xc == yc) return Object.Equals(x, y);
-
-            return false;
-        }
-
-        /// <summary>
-        /// Calculates the hash of a json DOM structure, without taking values into account
-        /// </summary>
-        /// <param name="x">the input json DOM structure</param>
-        /// <returns>A hash code</returns>
-        /// <remarks>
-        /// Theory says that two objects that are considered equal must have the same hash.
-        /// This means that we cannot use the hashes of the values because two equivalent
-        /// values (int 5) and (float 5.0f)  might have different hashes.
-        /// </remarks>
-        public static int GetStructureHashCode(Object x)
-        {
-            if (x == null) return 0;
-
-            if (x is IConvertible xval) { return 1; }
-
-            if (x is IReadOnlyList<object> xarr)
-            {
-                int h = xarr.Count;
-
-                for (int i = 0; i < xarr.Count; ++i)
-                {
-                    h ^= GetStructureHashCode(xarr[i]);
-                    h *= 17;
-                }
-
-                return h;
-            }
-
-            if (x is IReadOnlyDictionary<string, object> xdic)
-            {
-                int h = xdic.Count;
-
-                foreach (var kvp in xdic.OrderBy(item => item.Key))
-                {
-                    h ^= kvp.Key.GetHashCode(StringComparison.InvariantCulture);
-                    h ^= GetStructureHashCode(kvp.Value);
-                    h *= 17;
-                }
-
-                return h;
-            }
-
-            throw new ArgumentException($"Invalid type: {x.GetType()}", nameof(x));
-        }
-
-        #endregion
-    }
-
-    /// <summary>
-    /// Represents an inmutable Json Array.
-    /// </summary>
-    [System.Diagnostics.DebuggerDisplay("Json List")]
-    readonly struct _JsonArray : IReadOnlyList<object>, IJsonCollection, IList
-    {
-        #region constructor
-
-        public static bool TryCreate(Object value, out _JsonArray obj)
-        {
-            if (value is IConvertible) { obj = default; return false; }
-            if (value is IDictionary) { obj = default; return false; }
-            if (value is IEnumerable collection) { obj = _From(collection); return true; }
-
-            obj = default;
-            return false;
-        }
-
-        public static _JsonArray CreateFrom(JSONELEMENT array)
-        {
-            if (array.ValueKind != JsonValueKind.Array) throw new ArgumentException("Must be JsonValueKind.Array", nameof(array));
-
-            Object convert(JsonElement element)
-            {
-                return _JsonStaticUtils.Deserialize(element);
-            }
-
-            using (var entries = array.EnumerateArray())
-            {
-                return _From(entries.Select(convert));
-            }
-        }
-
-        private static _JsonArray _From(IEnumerable collection)
-        {
-            return new _JsonArray(TryClone(collection));
-        }
-
-        private _JsonArray(Array array)
-        {
-            _Array = array;
-        }
-
-        public object Clone() { return _From(this); }
-
-        private static Array TryClone(IEnumerable collection)
-        {
-            // 1st pass: determine element type and collection size
-
-            Type elementType = null;
-
-            int count = 0;
-
-            foreach (var item in collection.Cast<Object>())
-            {
-                if (item == null) throw new ArgumentException($"{nameof(collection)}[{count}] is null", nameof(collection));
-
-                ++count;
-
-                if (elementType == null)
-                {
-                    if (item is IConvertible) elementType = typeof(IConvertible);
-                    else elementType = item.GetType();
-                    continue;
-                }
-
-                // subsequent types must match elementType.
-                if (!elementType.IsAssignableFrom(item.GetType()))
-                {
-                    throw new ArgumentException($"{nameof(collection)}[{count}] is invalid type.", nameof(collection));
-                }
-            }
-
-            if (count == 0) { return Array.Empty<Object>(); }
-
-            if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>))
-            {
-                elementType = typeof(IDictionary);
-            }
-
-            int contentType = 0;
-            if (elementType == typeof(IConvertible)) contentType = 1;
-            if (contentType == 0 && typeof(IDictionary).IsAssignableFrom(elementType)) contentType = 3;
-            if (contentType == 0 && typeof(IEnumerable).IsAssignableFrom(elementType)) contentType = 2;
-
-            Array container = null;
-
-            switch (contentType)
-            {
-                case 1: container = Array.CreateInstance(typeof(IConvertible), count); break;
-                case 2: container = Array.CreateInstance(typeof(_JsonArray), count); break;
-                case 3: container = Array.CreateInstance(typeof(_JsonObject), count); break;
-                default: throw new NotImplementedException();
-            }
-
-            // 2nd pass: convert and assign items.
-
-            int idx = 0;
-            foreach (var item in collection) container.SetValue(_JsonStaticUtils.Serialize(item), idx++);
-
-            return container;
-        }
-
-        #endregion
-
-        #region data
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
-        private readonly Array _Array;
-
-        public override int GetHashCode()
-        {
-            if (_Array == null || _Array.Length == 0) return 0;
-
-            int h = 0;
-            foreach (var item in _Array)
-            {
-                h ^= item.GetHashCode();
-                h *= 17;
-            }
-
-            return h;
-        }
-
-        public override bool Equals(object obj)
-        {
-            return obj is IReadOnlyList<object> other && AreEqualByContent(this, other);
-        }
-
-        public static bool AreEqualByContent(IReadOnlyList<object> xarr, IReadOnlyList<object> yarr, float precission = 0)
-        {
-            if (Object.ReferenceEquals(xarr, yarr)) return true;
-            if (xarr == null) return false;
-            if (yarr == null) return false;
-            if (xarr.Count != yarr.Count) return false;
-
-            int c = xarr.Count;
-
-            for (int i = 0; i < c; ++i)
-            {
-                if (!_JsonStaticUtils.AreEqualByContent(xarr[i], yarr[i], precission)) return false;
-            }
-
-            return true;
-        }
-
-        #endregion
-
-        #region properties
-
-        public Object this[int index] => _Array.GetValue(index);
-
-        object IList.this[int index]
-        {
-            get => _Array.GetValue(index);
-            set => throw new NotSupportedException();
-        }
-
-        public bool IsArray => true;
-        public bool IsObject => false;
-        public int Count => _Array.Length;
-        bool IList.IsFixedSize => true;
-        bool IList.IsReadOnly => true;
-        bool ICollection.IsSynchronized => false;
-        object ICollection.SyncRoot => null;
-
-        #endregion
-
-        #region API
-        public IEnumerator<Object> GetEnumerator() { return _Array.Cast<object>().GetEnumerator(); }
-        IEnumerator IEnumerable.GetEnumerator() { return _Array.GetEnumerator(); }
-        public bool Contains(object value) { return IndexOf(value) >= 0; }
-        public int IndexOf(object value) { return Array.IndexOf(_Array, value); }
-        public void CopyTo(Array array, int index) { _Array.CopyTo(array, index); }
-        void IList.Clear() { throw new NotSupportedException(); }
-        void IList.Insert(int index, object value) { throw new NotSupportedException(); }
-        void IList.Remove(object value) { throw new NotSupportedException(); }
-        void IList.RemoveAt(int index) { throw new NotSupportedException(); }
-        int IList.Add(object value) { throw new NotSupportedException(); }
-
-        #endregion
-    }
-
-    /// <summary>
-    /// Represents an inmutable Json Object (a Dictionary).
-    /// </summary>
-    /// <remarks>
-    /// Supported by converter <see href="https://github.com/dotnet/runtime/blob/76904319b41a1dd0823daaaaae6e56769ed19ed3/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs"/>
-    /// </remarks>
-    [System.Diagnostics.DebuggerDisplay("Json Object")]
-    readonly struct _JsonObject : IReadOnlyDictionary<string, object>, IDictionary, IJsonCollection
-    {
-        #region constructor
-
-        public static bool TryCreate(Object value, out _JsonObject obj)
-        {
-            if (value is IConvertible _) { obj = default; return false; }
-            if (value is IDictionary dict0) { obj = new _JsonObject(_Enumerate(dict0)); return true; }
-            if (value is IReadOnlyDictionary<string, Object> dict1) { obj = new _JsonObject(_Enumerate(dict1)); return true; }
-
-            obj = default;
-            return false;
-        }
-
-        public static _JsonObject CreateFrom(JSONELEMENT dict)
-        {
-            if (dict.ValueKind != JsonValueKind.Object) throw new ArgumentException("Must be JsonValueKind.Object", nameof(dict));
-
-            JSONPROPERTY convert(JsonProperty property)
-            {
-                var value = _JsonStaticUtils.Deserialize(property.Value);
-                return new JSONPROPERTY(property.Name, value);
-            }
-
-            using (var entries = dict.EnumerateObject())
-            {
-                return new _JsonObject(dict.EnumerateObject().Select(convert));
-            }
-        }
-
-        private static IEnumerable<JSONPROPERTY> _Enumerate(IDictionary dict)
-        {
-            foreach (DictionaryEntry entry in dict)
-            {
-                if (entry.Value == null) continue;
-                yield return new JSONPROPERTY(_GetKey(entry.Key), _GetValue(entry.Value));
-            }
-        }
-
-        private static IEnumerable<JSONPROPERTY> _Enumerate<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> dict)
-        {
-            foreach (var entry in dict)
-            {
-                if (entry.Value == null) continue;
-                yield return new JSONPROPERTY(_GetKey(entry.Key), _GetValue(entry.Value));
-            }
-        }
-
-        private static string _GetKey(Object key)
-        {
-            if (key is string skey) return skey;
-            if (key is IConvertible ckey) return ckey.ToString(CULTURE.InvariantCulture);
-            return key.ToString();
-        }
-
-        private static Object _GetValue(Object value)
-        {
-            return _JsonStaticUtils.Serialize(value);
-        }
-
-        public object Clone()
-        {
-            return new _JsonObject(_Dictionary);
-        }
-
-        private _JsonObject(IEnumerable<JSONPROPERTY> items)
-        {
-            bool _filterEmptyCollections(JSONPROPERTY p)
-            {
-                if (p.Value is IConvertible) return true;
-                if (p.Value is IJsonCollection c) return c.Count > 0;
-                throw new ArgumentException($"{p.GetType().Name} not supported.", nameof(items));
-            }
-
-            items = items.Where(_filterEmptyCollections);
-
-            _Dictionary = new Dictionary<string, object>();
-
-            foreach (var item in items) _Dictionary.Add(item.Key, item.Value);
-        }
-
-        #endregion
-
-        #region data
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
-        private readonly Dictionary<String, Object> _Dictionary;
-
-        public override int GetHashCode()
-        {
-            if (_Dictionary == null || _Dictionary.Count == 0) return 0;
-
-            int h = 0;
-            foreach (var item in _Dictionary)
-            {
-                h ^= item.Key.GetHashCode(StringComparison.InvariantCulture);
-                h ^= item.Value.GetHashCode();
-                h *= 17;
-            }
-
-            return h;
-        }
-
-        public override bool Equals(object obj)
-        {
-            return obj is IReadOnlyDictionary<string, object> other && AreEqualByContent(this, other);
-        }
-
-        public static bool AreEqualByContent(IReadOnlyDictionary<string, object> xdic, IReadOnlyDictionary<string, object> ydic, float precission = 0)
-        {
-            if (Object.ReferenceEquals(xdic, ydic)) return true;
-            if (xdic == null) return false;
-            if (ydic == null) return false;
-            if (xdic.Count != ydic.Count) return false;
-
-            foreach (var key in xdic.Keys)
-            {
-                if (!xdic.TryGetValue(key, out Object xdval)) return false;
-                if (!ydic.TryGetValue(key, out Object ydval)) return false;
-
-                if (!_JsonStaticUtils.AreEqualByContent(xdval, ydval, precission)) return false;
-            }
-
-            return true;
-        }
-
-        #endregion
-
-        #region properties
-        public object this[string key] => _Dictionary[key];
-        public IEnumerable<string> Keys => _Dictionary.Keys;
-        ICollection IDictionary.Keys => _Dictionary.Keys;
-        public IEnumerable<object> Values => _Dictionary.Values;
-        ICollection IDictionary.Values => _Dictionary.Values;
-        public int Count => _Dictionary.Count;
-        public bool IsArray => false;
-        public bool IsObject => true;
-        public bool IsFixedSize => true;
-        public bool IsReadOnly => true;
-        public bool IsSynchronized => false;
-        public object SyncRoot => null;
-        public object this[object key]
-        {
-            get => this[(string)key];
-            set => throw new NotSupportedException();
-        }
-
-        #endregion
-
-        #region API
-
-        bool IDictionary.Contains(object key) { return ContainsKey((string)key); }
-        public bool ContainsKey(string key) { return _Dictionary.ContainsKey(key); }
-        public bool TryGetValue(string key, out object value) { return _Dictionary.TryGetValue(key, out value); }
-        IEnumerator IEnumerable.GetEnumerator() { return _Dictionary.GetEnumerator(); }
-        public IEnumerator<JSONPROPERTY> GetEnumerator() { return _Dictionary.AsEnumerable().GetEnumerator(); }
-        IDictionaryEnumerator IDictionary.GetEnumerator() { return ((IDictionary)_Dictionary).GetEnumerator(); }
-        void IDictionary.Add(object key, object value) { throw new NotSupportedException(); }
-        void IDictionary.Clear() { throw new NotSupportedException(); }
-        void IDictionary.Remove(object key) { throw new NotSupportedException(); }
-        void ICollection.CopyTo(Array array, int index) { throw new NotImplementedException(); }
-
-        #endregion
-    }
-}

+ 0 - 238
src/SharpGLTF.Core/IO/JsonContent.cs

@@ -1,238 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Json;
-
-using JSONOPTIONS = System.Text.Json.JsonSerializerOptions;
-
-namespace SharpGLTF.IO
-{
-    /// <summary>
-    /// Represents an inmutable json object stored in memory.
-    /// </summary>
-    /// <remarks>
-    /// The data structure is stored in memory as a DOM, using standard objects and collections.<br/>
-    /// Use <see cref="Serialize(object, JSONOPTIONS)"/> and <see cref="Deserialize{T}(JSONOPTIONS)"/> to convert to your types.<br/>
-    /// Use <see cref="Parse(JsonDocument)"/> and <see cref="ToJson(JSONOPTIONS)"/> to convert from/to raw json text.<br/>
-    /// </remarks>
-    [System.ComponentModel.ImmutableObject(true)]
-    [System.Diagnostics.DebuggerDisplay("{ToDebuggerDisplay(),nq}")]
-    public readonly struct JsonContent : ICloneable, IEquatable<JsonContent>
-    {
-        #region debug
-
-        internal const string _JsonTrimmingError1 = "JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.";
-
-        private string ToDebuggerDisplay()
-        {
-            if (_Content == null) return null;
-
-            var options = new JSONOPTIONS();
-            options.WriteIndented = true;
-            #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
-            return ToJson(options);
-            #pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
-        }
-
-        #endregion
-
-        #region constructors
-
-        public static implicit operator JsonContent(Boolean value) { return new JsonContent(value); }
-
-        public static implicit operator JsonContent(String value) { return new JsonContent(value); }
-
-        public static implicit operator JsonContent(Int32 value) { return new JsonContent(value); }
-
-        public static implicit operator JsonContent(Int64 value) { return new JsonContent(value); }
-
-        public static implicit operator JsonContent(Single value) { return new JsonContent(value); }
-
-        public static implicit operator JsonContent(Double value) { return new JsonContent(value); }
-
-        public static JsonContent CreateFrom(IConvertible value) { return new JsonContent(value); }
-        public static JsonContent CreateFrom(IList value) { return new JsonContent(value); }
-        public static JsonContent CreateFrom(IDictionary value) { return new JsonContent(value); }
-
-        internal static JsonContent _Wrap(Object value) { return new JsonContent(value); }
-
-        private JsonContent(Object value)
-        {
-            _Content = value == null ? null : _JsonStaticUtils.Serialize(value);
-
-            // don't store empty collections.
-            if (_Content is IJsonCollection collection && collection.Count == 0)
-                _Content = null;
-        }
-
-        Object ICloneable.Clone() { return new JsonContent(_Content); }
-
-        public JsonContent DeepClone() { return new JsonContent(_Content); }
-
-        #endregion
-
-        #region data
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private readonly Object _Content;
-
-        public override int GetHashCode()
-        {
-            return _Content == null ? 0 : _Content.GetHashCode();
-        }
-
-        public override bool Equals(object obj)
-        {
-            return obj is JsonContent other && Equals(other);
-        }
-
-        public bool Equals(JsonContent other)
-        {
-            return Object.Equals(this._Content, other._Content);
-        }
-
-        /// <summary>
-        /// Compares two <see cref="JsonContent"/> objects for equality.
-        /// </summary>
-        /// <param name="a">The first object to compare.</param>
-        /// <param name="b">The second object to compare.</param>
-        /// <param name="precission">The precission threshold when comparing floating point values.</param>
-        /// <returns>true if the objects are considered equal</returns>
-        /// <remarks>
-        /// - Comparing json structures is tricky because the values are typeless, so when we parse a json DOM
-        /// into memory we don't know which should be the right type to use for comparison.
-        /// - Also, System.Text.JSon is roundtrip safe when used in Net Core, but it is not when used in
-        /// Net Framework, so depending on the framework we use, floating point roundtrips will behave differently.
-        /// </remarks>
-        public static bool AreEqualByContent(JsonContent a, JsonContent b, float precission)
-        {
-            return _JsonStaticUtils.AreEqualByContent(a._Content, b._Content, precission);
-        }
-
-        #endregion
-
-        #region properties
-
-        /// <summary>
-        /// Gets the dynamic json structure.
-        /// </summary>
-        /// <remarks>
-        /// The possible value types can be:<br/>
-        /// - An <see cref="IConvertible"/> object.<br/>
-        /// - A non empty <see cref="IReadOnlyList{Object}"/> object.<br/>
-        /// - A non empty <see cref="IReadOnlyDictionary{String, Object}"/> object.
-        /// </remarks>
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)]
-        public Object Content => _Content;
-
-        #endregion
-
-        #region serialization
-
-        /// <summary>
-        /// Converts the value of a specified type into a <see cref="JsonContent"/> using <see cref="JsonSerializer"/>.
-        /// </summary>
-        /// <param name="value">The value to convert.</param>
-        /// <param name="options">Options to control the conversion behavior.</param>
-        /// <returns>A <see cref="JsonContent"/> object.</returns>
-        #if NET6_0_OR_GREATER
-        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(_JsonTrimmingError1)]
-        #endif
-        public static JsonContent Serialize(Object value, JSONOPTIONS options = null)
-        {
-            if (value == null) return default;
-
-            if (options == null)
-            {
-                options = new JSONOPTIONS
-                {
-                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
-                    DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
-                    WriteIndented = true
-                };
-            }
-
-            #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
-            var json = JsonSerializer.Serialize(value, value.GetType(), options);
-            #pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
-
-            return Parse(json);
-        }
-
-        /// <summary>
-        /// Parses a json text an converts it to a <see cref="JsonContent"/>
-        /// </summary>
-        /// <param name="jsonContent">The json text content.</param>
-        /// <param name="options">Parser options.</param>
-        /// <returns>A <see cref="JsonContent"/> object</returns>
-        public static JsonContent Parse(string jsonContent, JsonDocumentOptions options = default)
-        {
-            using (var doc = JsonDocument.Parse(jsonContent, options))
-            {
-                return Parse(doc);
-            }
-        }
-
-        public static JsonContent Parse(JsonDocument root)
-        {
-            return root == null ? default : new JsonContent(_JsonStaticUtils.Deserialize(root.RootElement));
-        }
-
-        #if NET6_0_OR_GREATER
-        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(_JsonTrimmingError1)]
-        #endif
-        public string ToJson(JSONOPTIONS options = null)
-        {
-            return _JsonStaticUtils.ToJson(_Content, options);
-        }
-
-        #if NET6_0_OR_GREATER
-        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(_JsonTrimmingError1)]
-        #endif
-        public Object Deserialize(Type type, JSONOPTIONS options = null)
-        {
-            return _JsonStaticUtils.Deserialize(_Content, type, options);
-        }
-
-        #if NET6_0_OR_GREATER
-        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(_JsonTrimmingError1)]
-        #endif
-        public T Deserialize<T>(JSONOPTIONS options = null)
-        {
-            return (T)_JsonStaticUtils.Deserialize(_Content, typeof(T), options);
-        }
-
-        #endregion
-
-        #region static API
-        public static bool IsJsonSerializable(Object value) { return IsJsonSerializable(value, out _); }
-
-        public static bool IsJsonSerializable(Object value, out Object invalidValue)
-        {
-            return _JsonStaticUtils.IsJsonSerializable(value, out invalidValue);
-        }
-
-        #endregion
-
-        #region API
-
-        public JsonContent GetNode(params IConvertible[] path)
-        {
-            Guard.NotNull(path, nameof(path));
-
-            var value = _JsonStaticUtils.GetNode(this._Content, path);
-            return new JsonContent(value);
-        }
-
-        public T GetValue<T>(params IConvertible[] path)
-            where T : IConvertible
-        {
-            Guard.NotNull(path, nameof(path));
-
-            return _JsonStaticUtils.GetValue<T>(this._Content, path);
-        }
-
-        #endregion
-    }
-}

+ 6 - 0
src/SharpGLTF.Core/IO/JsonSerializable.cs

@@ -254,6 +254,12 @@ namespace SharpGLTF.IO
 
 
             if (writer.TryWriteValue(value)) return;
             if (writer.TryWriteValue(value)) return;
 
 
+            if (value is System.Text.Json.Nodes.JsonNode jnode)
+            {
+                jnode.WriteTo(writer);
+                return;
+            }
+
             if (value is JsonSerializable vgltf) { vgltf.Serialize(writer); return; }
             if (value is JsonSerializable vgltf) { vgltf.Serialize(writer); return; }
 
 
             if (value is System.Collections.IDictionary dict)
             if (value is System.Collections.IDictionary dict)

+ 5 - 3
src/SharpGLTF.Core/IO/UnknownNode.cs

@@ -4,6 +4,8 @@ using System.Text;
 
 
 using System.Text.Json;
 using System.Text.Json;
 
 
+using JSONCONTENT = System.Text.Json.Nodes.JsonNode;
+
 namespace SharpGLTF.IO
 namespace SharpGLTF.IO
 {
 {
     /// <summary>
     /// <summary>
@@ -28,7 +30,7 @@ namespace SharpGLTF.IO
 
 
         private readonly string _Name;
         private readonly string _Name;
 
 
-        private readonly Dictionary<String, Object> _Properties = new Dictionary<String, Object>();
+        private readonly Dictionary<String, JSONCONTENT> _Properties = new Dictionary<String, JSONCONTENT>();
 
 
         #endregion
         #endregion
 
 
@@ -36,7 +38,7 @@ namespace SharpGLTF.IO
 
 
         public string Name => _Name;
         public string Name => _Name;
 
 
-        public IReadOnlyDictionary<String, Object> Properties => _Properties;
+        public IReadOnlyDictionary<String, JSONCONTENT> Properties => _Properties;
 
 
         #endregion
         #endregion
 
 
@@ -45,7 +47,7 @@ namespace SharpGLTF.IO
         protected override void DeserializeProperty(string property, ref Utf8JsonReader reader)
         protected override void DeserializeProperty(string property, ref Utf8JsonReader reader)
         {
         {
             reader.Read();
             reader.Read();
-            _Properties[property] = DeserializeUnknownObject(ref reader);
+            _Properties[property] = JSONCONTENT.Parse(ref reader);
         }
         }
 
 
         protected override void SerializeProperties(Utf8JsonWriter writer)
         protected override void SerializeProperties(Utf8JsonWriter writer)

+ 3 - 1
src/SharpGLTF.Core/Schema2/Serialization.WriteSettings.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.IO;
 using System.IO;
+using System.Text.Encodings.Web;
 
 
 using BYTES = System.ArraySegment<byte>;
 using BYTES = System.ArraySegment<byte>;
 using MODEL = SharpGLTF.Schema2.ModelRoot;
 using MODEL = SharpGLTF.Schema2.ModelRoot;
@@ -247,7 +248,8 @@ namespace SharpGLTF.Schema2
         {
         {
             var options = new System.Text.Json.JsonWriterOptions
             var options = new System.Text.Json.JsonWriterOptions
             {
             {
-                Indented = indented
+                Indented = indented,
+                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
             };
             };
 
 
             using (var mm = new System.IO.MemoryStream())
             using (var mm = new System.IO.MemoryStream())

+ 14 - 12
src/SharpGLTF.Core/Schema2/gltf.ExtraProperties.cs

@@ -2,20 +2,22 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 
 
-// using Newtonsoft.Json;
 using System.Text.Json;
 using System.Text.Json;
 
 
 using SharpGLTF.IO;
 using SharpGLTF.IO;
 
 
 using JsonToken = System.Text.Json.JsonTokenType;
 using JsonToken = System.Text.Json.JsonTokenType;
 
 
+// using JSONEXTRAS = SharpGLTF.IO.JsonContent;
+using JSONEXTRAS = System.Text.Json.Nodes.JsonNode;
+
 namespace SharpGLTF.Schema2
 namespace SharpGLTF.Schema2
 {
 {
     public interface IExtraProperties
     public interface IExtraProperties
     {
     {
         IReadOnlyCollection<JsonSerializable> Extensions { get; }
         IReadOnlyCollection<JsonSerializable> Extensions { get; }
 
 
-        IO.JsonContent Extras { get; set; }
+        JSONEXTRAS Extras { get; set; }
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -31,7 +33,7 @@ namespace SharpGLTF.Schema2
 
 
         private readonly List<JsonSerializable> _extensions = new List<JsonSerializable>();
         private readonly List<JsonSerializable> _extensions = new List<JsonSerializable>();
 
 
-        private IO.JsonContent _extras;
+        private JSONEXTRAS _extras;
 
 
         #endregion
         #endregion
 
 
@@ -45,7 +47,7 @@ namespace SharpGLTF.Schema2
         /// <summary>
         /// <summary>
         /// Gets or sets the extras content of this instance.
         /// Gets or sets the extras content of this instance.
         /// </summary>
         /// </summary>
-        public IO.JsonContent Extras
+        public JSONEXTRAS Extras
         {
         {
             get => _extras;
             get => _extras;
             set => _extras = value.DeepClone();
             set => _extras = value.DeepClone();
@@ -144,7 +146,7 @@ namespace SharpGLTF.Schema2
 
 
             foreach (var ext in this.Extensions) ext.ValidateReferences(validate);
             foreach (var ext in this.Extensions) ext.ValidateReferences(validate);
 
 
-            if (this._extras.Content is JsonSerializable js) js.ValidateReferences(validate);
+            // if (this._extras.Content is JsonSerializable js) js.ValidateReferences(validate);
         }
         }
 
 
         protected override void OnValidateContent(Validation.ValidationContext validate)
         protected override void OnValidateContent(Validation.ValidationContext validate)
@@ -156,9 +158,9 @@ namespace SharpGLTF.Schema2
                 lc.ValidateContent(validate);
                 lc.ValidateContent(validate);
             }
             }
 
 
-            if (this._extras.Content is JsonSerializable js) js.ValidateContent(validate);
+            // if (this._extras.Content is JsonSerializable js) js.ValidateContent(validate);
 
 
-            if (this._extras.Content != null) validate.IsJsonSerializable("Extras", this._extras.Content);
+            // if (this._extras.Content != null) validate.IsJsonSerializable("Extras", this._extras.Content);
         }
         }
 
 
         #endregion
         #endregion
@@ -178,9 +180,9 @@ namespace SharpGLTF.Schema2
             }
             }
 
 
             // todo, only write _extras if it's a known serializable type.
             // todo, only write _extras if it's a known serializable type.
-            var content = _extras.Content;
+            var content = _extras;
             if (content == null) return;
             if (content == null) return;
-            if (!IO.JsonContent.IsJsonSerializable(content)) return;
+            // if (!JSONEXTRAS.IsJsonSerializable(content)) return;
 
 
             SerializeProperty(writer, "extras", content);
             SerializeProperty(writer, "extras", content);
         }
         }
@@ -220,8 +222,8 @@ namespace SharpGLTF.Schema2
 
 
                 case "extras":
                 case "extras":
                     {
                     {
-                        var content = DeserializeUnknownObject(ref reader);
-                        _extras = JsonContent._Wrap(content);
+                        var content = System.Text.Json.Nodes.JsonNode.Parse(ref reader);
+                        _extras = content;
                         break;
                         break;
                     }
                     }
 
 
@@ -241,7 +243,7 @@ namespace SharpGLTF.Schema2
 
 
                     var val = ExtensionsFactory.Create(parent, key);
                     var val = ExtensionsFactory.Create(parent, key);
 
 
-                    if (val == null) val = new UnknownNode(key);
+                    val ??= new UnknownNode(key);
 
 
                     val.Deserialize(ref reader);
                     val.Deserialize(ref reader);
                     extensions.Add(val);
                     extensions.Add(val);

+ 1 - 7
src/SharpGLTF.Core/Validation/ValidationContext.Guards.cs

@@ -142,13 +142,7 @@ namespace SharpGLTF.Validation
             catch (ArgumentException ex) { _SchemaThrow(parameterName, ex.Message); }
             catch (ArgumentException ex) { _SchemaThrow(parameterName, ex.Message); }
             return this;
             return this;
         }
         }
-
-        public OUTTYPE IsJsonSerializable(PARAMNAME parameterName, Object value)
-        {
-            if (!IO.JsonContent.IsJsonSerializable(value, out Object invalidValue)) _SchemaThrow(parameterName, $"{invalidValue?.GetType()?.Name} cannot be serialized to Json");
-            return this;
-        }
-
+        
         #endregion
         #endregion
 
 
         #region link
         #region link

+ 1 - 1
src/SharpGLTF.Runtime/Runtime/RuntimeOptions.cs

@@ -39,7 +39,7 @@ namespace SharpGLTF.Runtime
 
 
         internal static Object ConvertExtras(Schema2.ExtraProperties source, RuntimeOptions options)
         internal static Object ConvertExtras(Schema2.ExtraProperties source, RuntimeOptions options)
         {
         {
-            if (source.Extras.Content == null) return null;
+            if (source.Extras == null) return null;
 
 
             if (options == null) return source.Extras;
             if (options == null) return source.Extras;
 
 

+ 18 - 10
src/SharpGLTF.Toolkit/BaseBuilder.cs

@@ -2,6 +2,8 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Text;
 using System.Text;
 
 
+using JSONEXTRAS = System.Text.Json.Nodes.JsonNode;
+
 namespace SharpGLTF
 namespace SharpGLTF
 {
 {
     public abstract class BaseBuilder
     public abstract class BaseBuilder
@@ -15,7 +17,7 @@ namespace SharpGLTF
             this.Name = name;
             this.Name = name;
         }
         }
 
 
-        protected BaseBuilder(string name, IO.JsonContent extras)
+        protected BaseBuilder(string name, JSONEXTRAS extras)
         {
         {
             this.Name = name;
             this.Name = name;
             this.Extras = extras;
             this.Extras = extras;
@@ -26,7 +28,7 @@ namespace SharpGLTF
             Guard.NotNull(other, nameof(other));
             Guard.NotNull(other, nameof(other));
 
 
             this.Name = other.Name;
             this.Name = other.Name;
-            this.Extras = other.Extras.DeepClone();
+            this.Extras = other.Extras.DeepClone();            
         }
         }
 
 
         #endregion
         #endregion
@@ -48,7 +50,7 @@ namespace SharpGLTF
         /// <summary>
         /// <summary>
         /// Gets or sets the custom data of this object.
         /// Gets or sets the custom data of this object.
         /// </summary>
         /// </summary>
-        public IO.JsonContent Extras { get; set; }
+        public JSONEXTRAS Extras { get; set; }
 
 
         protected static int GetContentHashCode(BaseBuilder x)
         protected static int GetContentHashCode(BaseBuilder x)
         {
         {
@@ -61,7 +63,9 @@ namespace SharpGLTF
 
 
             if (x.Name != y.Name) return false;
             if (x.Name != y.Name) return false;
 
 
-            return IO.JsonContent.AreEqualByContent(x.Extras, y.Extras, 0.0001f);
+            return x.Extras.DeepEquals(y.Extras, 0.0001f);
+
+            // return IO.JsonContent.AreEqualByContent(x.Extras, y.Extras, 0.0001f);
         }
         }
 
 
         #endregion
         #endregion
@@ -70,14 +74,18 @@ namespace SharpGLTF
 
 
         internal void SetNameAndExtrasFrom(BaseBuilder source)
         internal void SetNameAndExtrasFrom(BaseBuilder source)
         {
         {
-            this.Name = source.Name;
-            this.Extras = source.Extras.DeepClone();
+            this.Name = source?.Name;
+            this.Extras = source?.Extras?.DeepClone();
         }
         }
 
 
+        /// <summary>
+        /// Sets the name and extras from a Schema2 object.
+        /// </summary>
+        /// <param name="source"></param>
         internal void SetNameAndExtrasFrom(Schema2.LogicalChildOfRoot source)
         internal void SetNameAndExtrasFrom(Schema2.LogicalChildOfRoot source)
         {
         {
-            this.Name = source.Name;
-            this.Extras = source.Extras.DeepClone();
+            this.Name = source?.Name;
+            this.Extras = source?.Extras?.DeepClone();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -86,8 +94,8 @@ namespace SharpGLTF
         /// <param name="target">The target object</param>
         /// <param name="target">The target object</param>
         internal void TryCopyNameAndExtrasTo(Schema2.LogicalChildOfRoot target)
         internal void TryCopyNameAndExtrasTo(Schema2.LogicalChildOfRoot target)
         {
         {
-            if (this.Name != null) target.Name = this.Name;
-            if (this.Extras.Content != null) target.Extras = this.Extras.DeepClone();
+            target.Name = this?.Name;
+            target.Extras = this?.Extras?.DeepClone();
         }
         }
 
 
         #endregion
         #endregion

+ 3 - 1
src/SharpGLTF.Toolkit/Geometry/MeshBuilderToolkit.cs

@@ -7,6 +7,8 @@ using System.Text;
 
 
 using SharpGLTF.Geometry.VertexTypes;
 using SharpGLTF.Geometry.VertexTypes;
 
 
+using JSONEXTRAS = System.Text.Json.Nodes.JsonNode;
+
 namespace SharpGLTF.Geometry
 namespace SharpGLTF.Geometry
 {
 {
     /// <summary>
     /// <summary>
@@ -30,7 +32,7 @@ namespace SharpGLTF.Geometry
         /// <summary>
         /// <summary>
         /// Gets or sets the custom data of this object.
         /// Gets or sets the custom data of this object.
         /// </summary>
         /// </summary>
-        IO.JsonContent Extras { get; set; }
+        JSONEXTRAS Extras { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets a value indicating whether this mesh does not contain any geometry.
         /// Gets a value indicating whether this mesh does not contain any geometry.

+ 3 - 1
src/SharpGLTF.Toolkit/Geometry/Packed/PackedMeshBuilder.cs

@@ -5,6 +5,8 @@ using System.Linq;
 
 
 using SharpGLTF.Schema2;
 using SharpGLTF.Schema2;
 
 
+using JSONEXTRAS = System.Text.Json.Nodes.JsonNode;
+
 namespace SharpGLTF.Geometry
 namespace SharpGLTF.Geometry
 {
 {
     /// <summary>
     /// <summary>
@@ -98,7 +100,7 @@ namespace SharpGLTF.Geometry
             }
             }
         }
         }
 
 
-        private PackedMeshBuilder(string name, IO.JsonContent extras)
+        private PackedMeshBuilder(string name, JSONEXTRAS extras)
             : base(name, extras) { }
             : base(name, extras) { }
 
 
         #endregion
         #endregion

+ 4 - 3
src/SharpGLTF.Toolkit/Materials/ImageBuilder.cs

@@ -3,7 +3,8 @@ using System.Collections.Generic;
 using System.Text;
 using System.Text;
 
 
 using BYTES = System.ArraySegment<System.Byte>;
 using BYTES = System.ArraySegment<System.Byte>;
-using IMAGEFILE = SharpGLTF.Memory.MemoryImage;
+using IMAGEFILE = SharpGLTF.Memory.MemoryImage;
+using JSONEXTRAS = System.Text.Json.Nodes.JsonNode;
 
 
 namespace SharpGLTF.Materials
 namespace SharpGLTF.Materials
 {
 {
@@ -41,12 +42,12 @@ namespace SharpGLTF.Materials
             return content.IsEmpty ? null : new ImageBuilder(content, name, default);
             return content.IsEmpty ? null : new ImageBuilder(content, name, default);
         }
         }
 
 
-        public static ImageBuilder From(IMAGEFILE content, string name, IO.JsonContent extras)
+        public static ImageBuilder From(IMAGEFILE content, string name, JSONEXTRAS extras)
         {
         {
             return content.IsEmpty ? null : new ImageBuilder(content, name, extras);
             return content.IsEmpty ? null : new ImageBuilder(content, name, extras);
         }
         }
 
 
-        private ImageBuilder(IMAGEFILE content, string name, IO.JsonContent extras)
+        private ImageBuilder(IMAGEFILE content, string name, JSONEXTRAS extras)
             : base(name, extras)
             : base(name, extras)
         {
         {
             Content = content;
             Content = content;

+ 4 - 2
src/SharpGLTF.Toolkit/Scenes/InstanceBuilder.cs

@@ -3,6 +3,8 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
 
 
+using JSONEXTRAS = System.Text.Json.Nodes.JsonNode;
+
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
     /// <summary>
     /// <summary>
@@ -24,7 +26,7 @@ namespace SharpGLTF.Scenes
             return this;
             return this;
         }
         }
 
 
-        public InstanceBuilder WithExtras(IO.JsonContent extras)
+        public InstanceBuilder WithExtras(JSONEXTRAS extras)
         {
         {
             if (this.Content != null) this.Content.Extras = extras;
             if (this.Content != null) this.Content.Extras = extras;
             return this;
             return this;
@@ -59,7 +61,7 @@ namespace SharpGLTF.Scenes
         /// <summary>
         /// <summary>
         /// Gets the custom data of this object.
         /// Gets the custom data of this object.
         /// </summary>
         /// </summary>
-        public IO.JsonContent Extras => _ContentTransformer?.Extras ?? default;
+        public JSONEXTRAS Extras => _ContentTransformer?.Extras;
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the content of this instance.<br/>
         /// Gets or sets the content of this instance.<br/>

+ 2 - 1
src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs

@@ -5,6 +5,7 @@ using System.Numerics;
 
 
 using MATRIX = System.Numerics.Matrix4x4;
 using MATRIX = System.Numerics.Matrix4x4;
 using TRANSFORM = SharpGLTF.Transforms.AffineTransform;
 using TRANSFORM = SharpGLTF.Transforms.AffineTransform;
+using JSONEXTRAS = System.Text.Json.Nodes.JsonNode;
 
 
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
@@ -44,7 +45,7 @@ namespace SharpGLTF.Scenes
         public NodeBuilder(string name)
         public NodeBuilder(string name)
             : base(name) { }
             : base(name) { }
 
 
-        public NodeBuilder(string name, IO.JsonContent extras)
+        public NodeBuilder(string name, JSONEXTRAS extras)
             : base(name, extras) { }
             : base(name, extras) { }
 
 
         public Dictionary<NodeBuilder, NodeBuilder> DeepClone()
         public Dictionary<NodeBuilder, NodeBuilder> DeepClone()

+ 7 - 6
src/SharpGLTF.Toolkit/Scenes/Transformers.cs

@@ -8,6 +8,7 @@ using SharpGLTF.IO;
 
 
 using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.MaterialBuilder>;
 using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.MaterialBuilder>;
 using TRANSFORM = SharpGLTF.Transforms.AffineTransform;
 using TRANSFORM = SharpGLTF.Transforms.AffineTransform;
+using JSONEXTRAS = System.Text.Json.Nodes.JsonNode;
 
 
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
@@ -83,7 +84,7 @@ namespace SharpGLTF.Scenes
         /// <summary>
         /// <summary>
         /// Gets or sets the custom data of this object.
         /// Gets or sets the custom data of this object.
         /// </summary>
         /// </summary>
-        public abstract IO.JsonContent Extras { get; set; }
+        public abstract JSONEXTRAS Extras { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets the content of this transformer.<br/>
         /// Gets the content of this transformer.<br/>
@@ -231,7 +232,7 @@ namespace SharpGLTF.Scenes
         private String _NodeName;
         private String _NodeName;
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private IO.JsonContent _NodeExtras;
+        private JSONEXTRAS _NodeExtras;
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private NodeBuilder _ParentNode;
         private NodeBuilder _ParentNode;
@@ -251,7 +252,7 @@ namespace SharpGLTF.Scenes
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override JsonContent Extras
+        public override JSONEXTRAS Extras
         {
         {
             get => _NodeExtras;
             get => _NodeExtras;
             set => _NodeExtras = value;
             set => _NodeExtras = value;
@@ -329,7 +330,7 @@ namespace SharpGLTF.Scenes
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override JsonContent Extras
+        public override JSONEXTRAS Extras
         {
         {
             get => _Node.Extras;
             get => _Node.Extras;
             set => _Node.Extras = value;
             set => _Node.Extras = value;
@@ -403,7 +404,7 @@ namespace SharpGLTF.Scenes
         private String _NodeName;
         private String _NodeName;
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private IO.JsonContent _NodeExtras;
+        private JSONEXTRAS _NodeExtras;
 
 
         /// <summary>
         /// <summary>
         /// Defines the world matrix of the mesh at the time of binding.
         /// Defines the world matrix of the mesh at the time of binding.
@@ -427,7 +428,7 @@ namespace SharpGLTF.Scenes
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override JsonContent Extras
+        public override JSONEXTRAS Extras
         {
         {
             get => _NodeExtras;
             get => _NodeExtras;
             set => _NodeExtras = value;
             set => _NodeExtras = value;

+ 17 - 16
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -9,6 +9,7 @@ using SharpGLTF.Geometry;
 using SharpGLTF.Geometry.VertexTypes;
 using SharpGLTF.Geometry.VertexTypes;
 
 
 using MESHXFORM = SharpGLTF.Transforms.IGeometryTransform;
 using MESHXFORM = SharpGLTF.Transforms.IGeometryTransform;
+using JSONEXTRAS = System.Text.Json.Nodes.JsonNode;
 
 
 namespace SharpGLTF.Schema2
 namespace SharpGLTF.Schema2
 {
 {
@@ -373,24 +374,24 @@ namespace SharpGLTF.Schema2
             return instancing;
             return instancing;
         }
         }
 
 
-        public static MeshGpuInstancing WithInstanceCustomAccessors(this MeshGpuInstancing instancing, IReadOnlyList<IO.JsonContent> extras)
+        public static MeshGpuInstancing WithInstanceCustomAccessors(this MeshGpuInstancing instancing, IReadOnlyList<JSONEXTRAS> extras)
         {
         {
             Guard.NotNull(instancing, nameof(instancing));
             Guard.NotNull(instancing, nameof(instancing));
 
 
             // gather attribute keys
             // gather attribute keys
-            var keys = extras
-                .Select(item => item.Content)
-                .OfType<IReadOnlyDictionary<string, Object>>()
-                .SelectMany(item => item.Keys)
+            var keys = extras                
+                .OfType<System.Text.Json.Nodes.JsonObject>()
+                .SelectMany(item => item)
+                .Select(item => item.Key)
                 .Distinct()
                 .Distinct()
                 .Where(item => item.StartsWith("_", StringComparison.Ordinal));
                 .Where(item => item.StartsWith("_", StringComparison.Ordinal));
 
 
             foreach (var key in keys)
             foreach (var key in keys)
             {
             {
-                Object valueGetter(IO.JsonContent extra)
+                JSONEXTRAS valueGetter(JSONEXTRAS extra)
                 {
                 {
-                    if (!(extra.Content is IReadOnlyDictionary<string, Object> dict)) return null;
-                    return dict.TryGetValue(key, out var val) ? val : null;
+                    if (!(extra is System.Text.Json.Nodes.JsonObject dict)) return null;
+                    return dict.TryGetPropertyValue(key, out var val) ? val : null;
                 }
                 }
 
 
                 var values = extras.Select(valueGetter).ToList();
                 var values = extras.Select(valueGetter).ToList();
@@ -401,27 +402,27 @@ namespace SharpGLTF.Schema2
             return instancing;
             return instancing;
         }
         }
 
 
-        public static MeshGpuInstancing WithInstanceCustomAccessor(this MeshGpuInstancing instancing, string attribute, IReadOnlyList<Object> values)
+        public static MeshGpuInstancing WithInstanceCustomAccessor(this MeshGpuInstancing instancing, string attribute, IReadOnlyList<JSONEXTRAS> values)
         {
         {
             Guard.NotNullOrEmpty(attribute, nameof(attribute));
             Guard.NotNullOrEmpty(attribute, nameof(attribute));
 
 
             attribute = attribute.ToUpperInvariant();
             attribute = attribute.ToUpperInvariant();
-            var expectedType = values.Where(item => item != null).FirstOrDefault()?.GetType();
-            if (expectedType == null) return instancing;
+            var firstValue = values.Where(item => item != null).FirstOrDefault() as System.Text.Json.Nodes.JsonValue;
+            if (firstValue == null) return instancing;
 
 
-            if (expectedType == typeof(int))
+            if (firstValue.TryGetValue<int>(out _)) // assume all integers
             {
             {
-                var xValues = values.Select(item => item is int val ? val : 0).ToList();
+                var xValues = values.Select(item => item.GetValue<int>()).ToList();
                 return instancing.WithInstanceAccessor(attribute, xValues);
                 return instancing.WithInstanceAccessor(attribute, xValues);
             }
             }
 
 
-            if (expectedType == typeof(Single))
+            if (firstValue.TryGetValue<Single>(out _)) // assume all floats
             {
             {
-                var xValues = values.Select(item => item is float val ? val : 0).ToList();
+                var xValues = values.Select(item => item.GetValue<float>()).ToList();
                 return instancing.WithInstanceAccessor(attribute, xValues);
                 return instancing.WithInstanceAccessor(attribute, xValues);
             }
             }
 
 
-            throw new ArgumentException(expectedType.Name);
+            throw new ArgumentException(firstValue.ToString());
         }
         }
 
 
         #endregion
         #endregion

+ 14 - 69
tests/SharpGLTF.Core.Tests/IO/JsonContentTests.cs

@@ -124,22 +124,20 @@ namespace SharpGLTF.IO
                 var framework = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription;
                 var framework = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription;
                 return !framework.StartsWith(".NET Framework 4");
                 return !framework.StartsWith(".NET Framework 4");
             }
             }
-        }
-
-        
+        }        
 
 
-        public static bool AreEqual(JsonContent a, JsonContent b)
+        public static bool AreEqual(System.Text.Json.Nodes.JsonNode a, System.Text.Json.Nodes.JsonNode b)
         {
         {
-            if (Object.ReferenceEquals(a.Content, b.Content)) return true;
-            if (Object.ReferenceEquals(a.Content, null)) return false;
-            if (Object.ReferenceEquals(b.Content, null)) return false;
+            if (Object.ReferenceEquals(a, b)) return true;
+            if (Object.ReferenceEquals(a, null)) return false;
+            if (Object.ReferenceEquals(b, null)) return false;
 
 
             // A JsonContent ultimately represents a json block, so it seems fit to do the comparison that way.
             // A JsonContent ultimately represents a json block, so it seems fit to do the comparison that way.
             // also, there's the problem of floating point json writing, that can slightly change between
             // also, there's the problem of floating point json writing, that can slightly change between
             // different frameworks.
             // different frameworks.
 
 
-            var ajson = a.ToJson();
-            var bjson = b.ToJson();
+            var ajson = a.ToJsonString();
+            var bjson = b.ToJsonString();
 
 
             if (ajson == bjson) return true;
             if (ajson == bjson) return true;
 
 
@@ -148,66 +146,8 @@ namespace SharpGLTF.IO
             return false;
             return false;
             #endif
             #endif
 
 
-            return JsonContent.AreEqualByContent(a, b, 0.0001f);
-        }
-
-        [Test]
-        public void TestFloatingPointJsonRoundtrip()
-        {
-            float value = 1.1f; // serialized by system.text.json as 1.1000002f
-
-            var valueTxt = value.ToString("G9", System.Globalization.CultureInfo.InvariantCulture);
-
-            var dict = new Dictionary<string, Object>();            
-            dict["value"] = value;            
-
-            JsonContent a = JsonContent.CreateFrom(dict);
-
-            // roundtrip to json
-            var json = a.ToJson();
-            TestContext.Write(json);
-            var b = IO.JsonContent.Parse(json);            
-
-            Assert.IsTrue(AreEqual(a, b));            
-        }
-
-        [Test]
-        public void CreateJsonContent()
-        {
-            JsonContent a = JsonContent.CreateFrom(_TestStructure.CreateCompatibleDictionary());
-            
-            // roundtrip to json
-            var json = a.ToJson();
-            TestContext.Write(json);
-            var b = IO.JsonContent.Parse(json);            
-
-            // roundtrip to a runtime object
-            var x = a.Deserialize(typeof(_TestStructure));
-            var c = JsonContent.Serialize(x);
-
-            Assert.IsTrue(AreEqual(a, b));
-            Assert.IsTrue(AreEqual(a, c));            
-
-            foreach (var dom in new[] { a, b, c })
-            {
-                Assert.AreEqual("me", dom.GetValue<string>("author"));
-                Assert.AreEqual(17, dom.GetValue<int>("integer1"));
-                Assert.AreEqual(15.3f, dom.GetValue<float>("single1"));
-                Assert.AreEqual(3, dom.GetValue<int>("array1", 2));
-                Assert.AreEqual(2, dom.GetValue<int>("dict2", "d", "a1"));
-            }
-
-            Assert.AreEqual(b.GetHashCode(), c.GetHashCode());
-            Assert.AreEqual(b, c);
-
-            // clone & compare
-
-            var d = c.DeepClone();
-
-            Assert.AreEqual(c.GetHashCode(), d.GetHashCode());
-            Assert.AreEqual(c, d);
+            return true;            
         }
         }
-
     }
     }
 
 
     [Category("Core.IO")]
     [Category("Core.IO")]
@@ -225,9 +165,14 @@ namespace SharpGLTF.IO
 
 
             var model = Schema2.ModelRoot.CreateModel();
             var model = Schema2.ModelRoot.CreateModel();
 
 
+            var extras = new System.Text.Json.Nodes.JsonArray();
+            extras.Add(UNESCAPED);
+            extras.Add(ESCAPED);
+            extras.Add(UNESCAPED);
+
             model.Asset.Copyright = UNESCAPED;
             model.Asset.Copyright = UNESCAPED;
             model.UseScene(UNESCAPED);
             model.UseScene(UNESCAPED);
-            model.Asset.Extras = JsonContent.CreateFrom(new string[] { UNESCAPED, ESCAPED, UNESCAPED });
+            model.Asset.Extras = extras;
             model.CreateImage().Content = Memory.MemoryImage.DefaultPngImage;
             model.CreateImage().Content = Memory.MemoryImage.DefaultPngImage;
 
 
             // create write settings
             // create write settings

+ 8 - 6
tests/SharpGLTF.Core.Tests/Schema2/Authoring/BasicSceneCreationTests.cs

@@ -5,6 +5,8 @@ using NUnit.Framework;
 
 
 using SharpGLTF.IO;
 using SharpGLTF.IO;
 
 
+using JSONEXTRAS = System.Text.Json.Nodes.JsonNode;
+
 namespace SharpGLTF.Schema2.Authoring
 namespace SharpGLTF.Schema2.Authoring
 {
 {
     using VPOSNRM = Geometry.VertexBuilder<Geometry.VertexTypes.VertexPositionNormal,Geometry.VertexTypes.VertexEmpty,Geometry.VertexTypes.VertexEmpty>;
     using VPOSNRM = Geometry.VertexBuilder<Geometry.VertexTypes.VertexPositionNormal,Geometry.VertexTypes.VertexEmpty,Geometry.VertexTypes.VertexEmpty>;
@@ -46,9 +48,9 @@ namespace SharpGLTF.Schema2.Authoring
                 ["C"] = new List<int> { 4, 6, 7 },
                 ["C"] = new List<int> { 4, 6, 7 },
                 ["D"] = new Dictionary<string, int> { ["S"]= 1, ["T"] = 2 }
                 ["D"] = new Dictionary<string, int> { ["S"]= 1, ["T"] = 2 }
             };
             };
-            dict["dict2"] = new Dictionary<string, int> { ["2"] = 2, ["3"] = 3 };
+            dict["dict2"] = new Dictionary<string, int> { ["2"] = 2, ["3"] = 3 };            
 
 
-            var extras = JsonContent.CreateFrom(dict);
+            var extras = JSONEXTRAS.Parse(System.Text.Json.JsonSerializer.Serialize(dict));
 
 
             root.Extras = extras;
             root.Extras = extras;
             
             
@@ -57,15 +59,15 @@ namespace SharpGLTF.Schema2.Authoring
 
 
             var a = root.Extras;
             var a = root.Extras;
             var b = rootBis.Extras;
             var b = rootBis.Extras;
-            var json = rootBis.Extras.ToJson();
-            var c = IO.JsonContent.Parse(json);
+            var json = rootBis.Extras.ToJsonString();
+            var c = JSONEXTRAS.Parse(json);
 
 
-            Assert.IsTrue(JsonContentTests.AreEqual(a,b));
+            Assert.IsTrue(JsonContentTests.AreEqual(a, b));
             Assert.IsTrue(JsonContentTests.AreEqual(a, extras));
             Assert.IsTrue(JsonContentTests.AreEqual(a, extras));
             Assert.IsTrue(JsonContentTests.AreEqual(b, extras));
             Assert.IsTrue(JsonContentTests.AreEqual(b, extras));
             Assert.IsTrue(JsonContentTests.AreEqual(c, extras));
             Assert.IsTrue(JsonContentTests.AreEqual(c, extras));
 
 
-            Assert.AreEqual(2, c.GetValue<int>("dict1","D","T"));
+            // Assert.AreEqual(2, c.GetValue<int>("dict1","D","T"));
         }
         }
 
 
         [Test(Description = "Creates a model with a triangle mesh")]
         [Test(Description = "Creates a model with a triangle mesh")]

+ 3 - 0
tests/SharpGLTF.NUnit/TestFiles.cs

@@ -193,6 +193,9 @@ namespace SharpGLTF
                 "\\meshes\\KHR_materials_volume_testing.glb", // draco compression-
                 "\\meshes\\KHR_materials_volume_testing.glb", // draco compression-
                 "\\meshes\\Yeti\\MayaExport\\", // validator reports out of bounds accesor
                 "\\meshes\\Yeti\\MayaExport\\", // validator reports out of bounds accesor
                 "\\meshes\\Demos\\retargeting\\riggedMesh.glb", // validator reports errors
                 "\\meshes\\Demos\\retargeting\\riggedMesh.glb", // validator reports errors
+                "\\meshes\\Buildings\\road gap.glb", // uses KHR_Draco compression  
+                "\\meshes\\Buildings\\Road corner.glb", // uses KHR_Draco compression  
+                "\\meshes\\Tests\\BadDraco\\Box-draco.glb", // uses KHR_Draco compression  
             };            
             };            
 
 
             var files = GetModelPathsInDirectory(_BabylonJsMeshesDir);
             var files = GetModelPathsInDirectory(_BabylonJsMeshesDir);

+ 12 - 9
tests/SharpGLTF.Toolkit.Tests/Materials/ContentSharingTests.cs

@@ -3,8 +3,9 @@ using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Numerics;
 using System.Numerics;
-using System.Text;
-
+using System.Text;
+using System.Text.Json;
+
 using NUnit.Framework;
 using NUnit.Framework;
 
 
 using SharpGLTF.Geometry.Parametric;
 using SharpGLTF.Geometry.Parametric;
@@ -31,27 +32,29 @@ namespace SharpGLTF.Materials
 
 
             var material2 = material1.Clone();
             var material2 = material1.Clone();
 
 
-            Assert.IsTrue(MaterialBuilder.AreEqualByContent(material1, material2));
+            Assert.IsTrue(MaterialBuilder.AreEqualByContent(material1, material2));
+
+            var extras = new System.Text.Json.Nodes.JsonObject();
+            extras["hello"] = 1;
 
 
             material2
             material2
                 .GetChannel(KnownChannel.BaseColor)
                 .GetChannel(KnownChannel.BaseColor)
                 .Texture
                 .Texture
                 .PrimaryImage
                 .PrimaryImage
-                .Extras = IO.JsonContent.Serialize(new KeyValuePair<int, string>(1, "hello"));
+                .Extras = extras;
 
 
             var material3 = material2.Clone();
             var material3 = material2.Clone();
 
 
             Assert.IsFalse(MaterialBuilder.AreEqualByContent(material1, material2));
             Assert.IsFalse(MaterialBuilder.AreEqualByContent(material1, material2));
             Assert.IsTrue(MaterialBuilder.AreEqualByContent(material2, material3));
             Assert.IsTrue(MaterialBuilder.AreEqualByContent(material2, material3));
 
 
-            var kvp = material3.GetChannel(KnownChannel.BaseColor)
+            var dict = material3.GetChannel(KnownChannel.BaseColor)
                 .Texture
                 .Texture
                 .PrimaryImage
                 .PrimaryImage
-                .Extras.Deserialize<KeyValuePair<int, string>>();
-
-            Assert.AreEqual(kvp.Key, 1);
-            Assert.AreEqual(kvp.Value, "hello");
+                .Extras.Deserialize<Dictionary<string,int>>();            
 
 
+            Assert.AreEqual(1, dict.Count);
+            Assert.AreEqual(1, dict["hello"]);
         }
         }
 
 
         [Test]
         [Test]

+ 5 - 1
tests/SharpGLTF.Toolkit.Tests/Scenes/SceneBuilderTests.cs

@@ -122,7 +122,11 @@ namespace SharpGLTF.Scenes
 
 
             var material = MaterialBuilder.CreateDefault();
             var material = MaterialBuilder.CreateDefault();
             material.Name = "hello name";
             material.Name = "hello name";
-            material.Extras = IO.JsonContent.Serialize(new KeyValuePair<string, int>("hello", 16));
+
+            var extras = new System.Text.Json.Nodes.JsonObject();
+            extras["hello"] = 16;
+
+            material.Extras = extras;
 
 
             var mesh = new Cube<MaterialBuilder>(material).ToMesh(Matrix4x4.Identity);
             var mesh = new Cube<MaterialBuilder>(material).ToMesh(Matrix4x4.Identity);
             mesh.Name = "world name";
             mesh.Name = "world name";