Skip to content

Commit 464cdbb

Browse files
committed
Json.NET Unity Converters 1.6.3
Based on commit 5aa91cb Created by CircleCI job Build #2167 https://circleci.com/gh/applejag/Newtonsoft.Json-for-Unity.Converters/2167
1 parent 3fc2be6 commit 464cdbb

File tree

6 files changed

+176
-37
lines changed

6 files changed

+176
-37
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Unity Converters for Newtonsoft.Json changelog
22

3+
## 1.6.3 (2024-03-01)
4+
5+
- Fixed converter lookups collisions when multiple assemblies converters
6+
with the same name, as it was not resolving assemblies in an exact way
7+
nor deterministic order:
8+
9+
- Added assembly name field to `ConverterConfig` for each converter.
10+
11+
- Changed TypeCache to sort assemblies based on `FullName`
12+
and some heuristics.
13+
14+
- Changed TypeCache to lookup type in correct assembly,
15+
based on the assembly's name.
16+
17+
Thanks [@Erifirin](https://github.com/Erifirin) for the pull request ([#90](https://github.com/jilleJr/Newtonsoft.Json-for-Unity.Converters/pull/90))
18+
319
## 1.6.2 (2024-01-08)
420

521
- Fixed typo in the new Unity.Mathematics QuaternionConverter's namespace:

Configuration/ConverterConfig.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ public struct ConverterConfig : IEquatable<ConverterConfig>
1212

1313
public string converterName;
1414

15+
public string converterAssembly;
16+
1517
public List<KeyedConfig> settings;
1618

1719
public override string ToString()
1820
{
19-
return $"{{enabled={enabled}, converterName={converterName}, settings=[{settings?.Count ?? 0}]}}";
21+
return $"{{enabled={enabled}, converterName={converterName}, assembly={converterAssembly}, settings=[{settings?.Count ?? 0}]}}";
2022
}
2123

2224
public override bool Equals(object obj)
@@ -28,16 +30,18 @@ public bool Equals(ConverterConfig other)
2830
{
2931
return enabled == other.enabled &&
3032
converterName == other.converterName &&
33+
converterAssembly == other.converterAssembly &&
3134
EqualityComparer<List<KeyedConfig>>.Default.Equals(settings, other.settings);
3235
}
3336

3437
#pragma warning disable S2328 // "GetHashCode" should not reference mutable fields
3538
public override int GetHashCode()
3639
#pragma warning restore S2328 // "GetHashCode" should not reference mutable fields
3740
{
38-
int hashCode = 1016066258;
41+
int hashCode = 913629501;
3942
hashCode = hashCode * -1521134295 + enabled.GetHashCode();
4043
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(converterName);
44+
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(converterAssembly);
4145
hashCode = hashCode * -1521134295 + EqualityComparer<List<KeyedConfig>>.Default.GetHashCode(settings);
4246
return hashCode;
4347
}

Editor/UnityConvertersConfigEditor.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,27 @@ public override void OnInspectorGUI()
150150

151151
private void AddAndSetupConverters(SerializedProperty arrayProperty, IList<Type> converterTypes, bool newAreEnabledByDefault)
152152
{
153-
var elements = EnumerateArrayElements(arrayProperty);
153+
var elements = EnumerateArrayElements(arrayProperty).ToArray();
154154
var elementTypes = elements
155-
.Select(e => TypeCache.FindType(e.FindPropertyRelative(nameof(ConverterConfig.converterName)).stringValue))
155+
.Select(e => TypeCache.FindType(
156+
name: e.FindPropertyRelative(nameof(ConverterConfig.converterName)).stringValue,
157+
assemblyName: e.FindPropertyRelative(nameof(ConverterConfig.converterAssembly)).stringValue
158+
))
156159
.ToArray();
157160

161+
// Refresh missing fields on existing types
162+
for (int i = 0; i < elements.Length; i++)
163+
{
164+
SerializedProperty elem = elements[i];
165+
Type type = elementTypes[i];
166+
167+
var assemblyNameProp = elem.FindPropertyRelative(nameof(ConverterConfig.converterAssembly));
168+
if (string.IsNullOrEmpty(assemblyNameProp.stringValue))
169+
{
170+
assemblyNameProp.stringValue = type.Assembly.GetName().Name;
171+
}
172+
}
173+
158174
Type[] missingConverters = converterTypes
159175
.Where(type => !elementTypes.Contains(type))
160176
.ToArray();
@@ -166,7 +182,7 @@ private void AddAndSetupConverters(SerializedProperty arrayProperty, IList<Type>
166182
{
167183
continue;
168184
}
169-
var typeName = arrayProperty.GetArrayElementAtIndex(i).FindPropertyRelative(nameof(ConverterConfig.converterName)).stringValue;
185+
string typeName = arrayProperty.GetArrayElementAtIndex(i).FindPropertyRelative(nameof(ConverterConfig.converterName)).stringValue;
170186
Debug.Log($"Removed type from JsonConverter list: \"{typeName}\"", target);
171187
arrayProperty.DeleteArrayElementAtIndex(i);
172188
}
@@ -179,9 +195,11 @@ private void AddAndSetupConverters(SerializedProperty arrayProperty, IList<Type>
179195
SerializedProperty elemProp = arrayProperty.GetArrayElementAtIndex(nextIndex);
180196
SerializedProperty enabledProp = elemProp.FindPropertyRelative(nameof(ConverterConfig.enabled));
181197
SerializedProperty converterNameProp = elemProp.FindPropertyRelative(nameof(ConverterConfig.converterName));
198+
SerializedProperty assemblyNameProp = elemProp.FindPropertyRelative(nameof(ConverterConfig.converterAssembly));
182199

183200
enabledProp.boolValue = newAreEnabledByDefault;
184201
converterNameProp.stringValue = converterType.FullName;
202+
assemblyNameProp.stringValue = converterType.Assembly.GetName().Name;
185203
}
186204
}
187205

@@ -236,7 +254,10 @@ private void FoldoutConvertersList(SerializedProperty property, AnimBool fadedAn
236254
var allConfigsWithType = EnumerateArrayElements(property)
237255
.Select(o => (
238256
serializedProperty: o,
239-
type: TypeCache.FindType(o.FindPropertyRelative(nameof(ConverterConfig.converterName)).stringValue)
257+
type: TypeCache.FindType(
258+
name: o.FindPropertyRelative(nameof(ConverterConfig.converterName)).stringValue,
259+
assemblyName: o.FindPropertyRelative(nameof(ConverterConfig.converterAssembly)).stringValue
260+
)
240261
))
241262
.Where(o => o.type != null)
242263
.OrderBy(o => o.type.FullName);

UnityConverters/UnityConverterInitializer.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,15 +157,15 @@ internal static ConverterGrouping FindGroupedConverters(UnityConvertersConfig co
157157
}
158158

159159
return new ConverterGrouping {
160-
outsideConverters = config.outsideConverters.Select(x => GetTypeOrLog(x.converterName)).WhereNotNullRef().ToList(),
161-
unityConverters = config.unityConverters.Select(x => GetTypeOrLog(x.converterName)).WhereNotNullRef().ToList(),
162-
jsonNetConverters = config.jsonNetConverters.Select(x => GetTypeOrLog(x.converterName)).WhereNotNullRef().ToList(),
160+
outsideConverters = config.outsideConverters.Select(x => GetTypeOrLog(x.converterName, x.converterAssembly)).WhereNotNullRef().ToList(),
161+
unityConverters = config.unityConverters.Select(x => GetTypeOrLog(x.converterName, x.converterAssembly)).WhereNotNullRef().ToList(),
162+
jsonNetConverters = config.jsonNetConverters.Select(x => GetTypeOrLog(x.converterName, x.converterAssembly)).WhereNotNullRef().ToList(),
163163
};
164164
}
165165

166-
private static Type GetTypeOrLog(string name)
166+
private static Type GetTypeOrLog(string name, string assemblyName)
167167
{
168-
var type = TypeCache.FindType(name);
168+
var type = TypeCache.FindType(name, assemblyName);
169169
if (type == null)
170170
{
171171
Debug.LogWarning($"Failed to lookup JsonConverter type. Ignoring it. Type name: \"{name}\""+
@@ -215,7 +215,7 @@ private static IEnumerable<Type> ApplyConfigFilter(IEnumerable<Type> types, bool
215215

216216
var typesOfEnabledThroughConfig = configs
217217
.Where(o => o.enabled)
218-
.Select(o => Utility.TypeCache.FindType(o.converterName))
218+
.Select(o => Utility.TypeCache.FindType(o.converterName, o.converterAssembly))
219219
.Where(o => o != null);
220220

221221
var hashMap = new HashSet<Type>(typesOfEnabledThroughConfig);

Utility/TypeCache.cs

Lines changed: 122 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,44 +7,142 @@ namespace Newtonsoft.Json.UnityConverters.Utility
77
{
88
internal static class TypeCache
99
{
10-
private static readonly Dictionary<string, Type> _typeByName
11-
= new Dictionary<string, Type>();
12-
13-
private static readonly Assembly[] _assemblies
14-
= AppDomain.CurrentDomain.GetAssemblies()
15-
// Reversing so we get last imported assembly first.
16-
// When searching for types we want to look in mscorlib last
17-
// and Newtonsoft.Json up as the first ones
18-
.Reverse()
19-
.ToArray();
20-
21-
public static Type FindType(string name)
10+
private static readonly Dictionary<ValueTuple<string, string>, Type> _typeByNameAndAssembly
11+
= new Dictionary<ValueTuple<string, string>, Type>();
12+
private static readonly Dictionary<string, Assembly> _assemblyByName
13+
= new Dictionary<string, Assembly>();
14+
15+
private static readonly Assembly[] _assemblies;
16+
17+
static TypeCache()
2218
{
23-
if (_typeByName.TryGetValue(name, out var type))
19+
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
20+
.Select(x => new NamedAssembly(x))
21+
.Where(x => x.name != "Microsoft.GeneratedCode")
22+
.OrderBy(AssemblyOrderBy).ThenBy(x => x.name)
23+
.ToList();
24+
25+
_assemblies = new Assembly[assemblies.Count];
26+
_assemblyByName = new Dictionary<string, Assembly>(assemblies.Count);
27+
28+
for (int i = 0; i < assemblies.Count; i++)
2429
{
25-
return type;
30+
NamedAssembly assembly = assemblies[i];
31+
_assemblies[i] = assembly.assembly;
32+
33+
// Adding them like this because the LINQ ToDictionary does not like duplicate keys
34+
_assemblyByName[assembly.name] = assembly.assembly;
2635
}
27-
else
36+
}
37+
38+
private static int AssemblyOrderBy(NamedAssembly record)
39+
{
40+
// Newtonsoft.Json converters should be among the first, as they're most commonly referenced
41+
if (record.name.StartsWith("Newtonsoft.Json"))
2842
{
29-
// Check this assembly, or if it has AssemblyQualifiedName
30-
type = Type.GetType(name);
31-
if (type != null)
43+
return -10;
44+
}
45+
46+
// Relies on the heuristic that "assembly name == namespace"
47+
switch (GetRootNamespace(record.name))
48+
{
49+
// User-defined code gets sent to the top
50+
case "Assembly-CSharp":
51+
return -1;
52+
case "Assembly-CSharp-Editor":
53+
return -2;
54+
55+
// Unity standard library does not contain any converters
56+
case "Unity":
57+
return 10;
58+
case "UnityEngine":
59+
return 11;
60+
case "UnityEditor":
61+
return 12;
62+
63+
// .NET standard library does not contain any converters, so just put it at the end
64+
case "System":
65+
return 20;
66+
case "netstandard":
67+
return 21;
68+
case "mscorlib":
69+
return 22;
70+
71+
default:
72+
return 0;
73+
}
74+
}
75+
76+
private static string GetRootNamespace(string name)
77+
{
78+
int index = name.IndexOf('.');
79+
if (index > 0)
80+
{
81+
return name.Substring(0, index);
82+
}
83+
84+
return name;
85+
}
86+
87+
public static Type FindType(string name, string assemblyName)
88+
{
89+
Type type;
90+
if (assemblyName != null)
91+
{
92+
if (_typeByNameAndAssembly.TryGetValue((name, assemblyName), out type))
3293
{
33-
_typeByName[name] = type;
3494
return type;
3595
}
3696

37-
// Check all the other assemblies, from last imported to first
38-
foreach (var assembly in _assemblies)
97+
if (_assemblyByName.TryGetValue(assemblyName, out var asm))
3998
{
40-
type = assembly.GetType(name);
99+
type = asm.GetType(name);
41100
if (type != null)
42101
{
43-
_typeByName[name] = type;
102+
_typeByNameAndAssembly[(name, assemblyName)] = type;
44103
return type;
45104
}
46105
}
47-
return null;
106+
}
107+
else
108+
{
109+
if (_typeByNameAndAssembly.TryGetValue((name, null), out type))
110+
{
111+
return type;
112+
}
113+
}
114+
115+
// Check this assembly, or if it has AssemblyQualifiedName
116+
type = Type.GetType(name);
117+
if (type != null)
118+
{
119+
_typeByNameAndAssembly[(name, null)] = type;
120+
return type;
121+
}
122+
123+
// Check all the other assemblies, from last imported to first
124+
foreach (var assembly in _assemblies)
125+
{
126+
type = assembly.GetType(name);
127+
if (type != null)
128+
{
129+
_typeByNameAndAssembly[(name, null)] = type;
130+
return type;
131+
}
132+
}
133+
134+
return null;
135+
}
136+
137+
private readonly struct NamedAssembly
138+
{
139+
public Assembly assembly { get; }
140+
public string name { get; }
141+
142+
public NamedAssembly(Assembly assembly)
143+
{
144+
this.assembly = assembly;
145+
name = assembly.GetName().Name;
48146
}
49147
}
50148
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "jillejr.newtonsoft.json-for-unity.converters",
33
"displayName": "Json.NET Converters of Unity types",
4-
"version": "1.6.2",
4+
"version": "1.6.3",
55
"unity": "2018.1",
66
"description": "This package contains converters to and from common Unity types for Newtonsoft.Json. Types such as Vector2, Vector3, Matrix4x4, Quaternions, Color, even ScriptableObject, and more.\n\nGoes hand in hand with the jillejr.newtonsoft.json-for-unity package.\n\nThis package is licensed under The MIT License (MIT)\n\nCopyright © 2019 Kalle Fagerberg (jilleJr)\nhttps://github.com/jilleJr/Newtonsoft.Json-for-Unity.Converters\n\nCopyright © 2020 Wanzyee Studio\nhttp://wanzyeestudio.blogspot.com/2017/03/jsonnet-converters.html\n\nCopyright © 2007 ParentElement\nhttps://github.com/ianmacgillivray/Json-NET-for-Unity\n\nCopyright © 2017 .NET Foundation and Contributors\nhttps://github.com/dotnet/runtime\n\nSee full copyrights in LICENSE.md inside package",
77
"main": "index.js",

0 commit comments

Comments
 (0)