2828using System ;
2929using System . Collections ;
3030using System . Collections . Generic ;
31- using System . ComponentModel ;
3231#if NET20
3332using Newtonsoft . Json . Utilities . LinqBridge ;
3433#else
@@ -46,9 +45,67 @@ namespace Newtonsoft.Json.Converters
4645 /// </summary>
4746 public class DiscriminatedUnionConverter : JsonConverter
4847 {
48+ #region UnionDefinition
49+ internal class Union
50+ {
51+ public List < UnionCase > Cases ;
52+ public Converter < object , int > TagReader { get ; set ; }
53+ }
54+
55+ internal class UnionCase
56+ {
57+ public int Tag ;
58+ public string Name ;
59+ public PropertyInfo [ ] Fields ;
60+ public Converter < object , object [ ] > FieldReader ;
61+ public Converter < object [ ] , object > Constructor ;
62+ }
63+ #endregion
64+
4965 private const string CasePropertyName = "Case" ;
5066 private const string FieldsPropertyName = "Fields" ;
5167
68+ private static readonly ThreadSafeStore < Type , Union > UnionCache = new ThreadSafeStore < Type , Union > ( CreateUnion ) ;
69+ private static readonly ThreadSafeStore < Type , Type > UnionTypeLookupCache = new ThreadSafeStore < Type , Type > ( CreateUnionTypeLookup ) ;
70+
71+ private static Type CreateUnionTypeLookup ( Type t )
72+ {
73+ // this lookup is because cases with fields are derived from union type
74+ // need to get declaring type to avoid duplicate Unions in cache
75+
76+ // hacky but I can't find an API to get the declaring type without GetUnionCases
77+ object [ ] cases = ( object [ ] ) FSharpUtils . GetUnionCases ( null , t , null ) ;
78+
79+ object caseInfo = cases . First ( ) ;
80+
81+ Type unionType = ( Type ) FSharpUtils . GetUnionCaseInfoDeclaringType ( caseInfo ) ;
82+ return unionType ;
83+ }
84+
85+ private static Union CreateUnion ( Type t )
86+ {
87+ Union u = new Union ( ) ;
88+
89+ u . TagReader = ( Converter < object , int > ) FSharpUtils . PreComputeUnionTagReader ( null , t , null ) ;
90+ u . Cases = new List < UnionCase > ( ) ;
91+
92+ object [ ] cases = ( object [ ] ) FSharpUtils . GetUnionCases ( null , t , null ) ;
93+
94+ foreach ( object unionCaseInfo in cases )
95+ {
96+ UnionCase unionCase = new UnionCase ( ) ;
97+ unionCase . Tag = ( int ) FSharpUtils . GetUnionCaseInfoTag ( unionCaseInfo ) ;
98+ unionCase . Name = ( string ) FSharpUtils . GetUnionCaseInfoName ( unionCaseInfo ) ;
99+ unionCase . Fields = ( PropertyInfo [ ] ) FSharpUtils . GetUnionCaseInfoFields ( unionCaseInfo ) ;
100+ unionCase . FieldReader = ( Converter < object , object [ ] > ) FSharpUtils . PreComputeUnionReader ( null , unionCaseInfo , null ) ;
101+ unionCase . Constructor = ( Converter < object [ ] , object > ) FSharpUtils . PreComputeUnionConstructor ( null , unionCaseInfo , null ) ;
102+
103+ u . Cases . Add ( unionCase ) ;
104+ }
105+
106+ return u ;
107+ }
108+
52109 /// <summary>
53110 /// Writes the JSON representation of the object.
54111 /// </summary>
@@ -59,21 +116,21 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
59116 {
60117 DefaultContractResolver resolver = serializer . ContractResolver as DefaultContractResolver ;
61118
62- Type t = value . GetType ( ) ;
119+ Type unionType = UnionTypeLookupCache . Get ( value . GetType ( ) ) ;
120+ Union union = UnionCache . Get ( unionType ) ;
63121
64- object result = FSharpUtils . GetUnionFields ( null , value , t , null ) ;
65- object info = FSharpUtils . GetUnionCaseInfo ( result ) ;
66- object fields = FSharpUtils . GetUnionCaseFields ( result ) ;
67- object caseName = FSharpUtils . GetUnionCaseInfoName ( info ) ;
68- object [ ] fieldsAsArray = fields as object [ ] ;
122+ int tag = union . TagReader ( value ) ;
123+ UnionCase caseInfo = union . Cases . Single ( c => c . Tag == tag ) ;
69124
70125 writer . WriteStartObject ( ) ;
71126 writer . WritePropertyName ( ( resolver != null ) ? resolver . GetResolvedPropertyName ( CasePropertyName ) : CasePropertyName ) ;
72- writer . WriteValue ( ( string ) caseName ) ;
73- if ( fieldsAsArray != null && fieldsAsArray . Length > 0 )
127+ writer . WriteValue ( caseInfo . Name ) ;
128+ if ( caseInfo . Fields != null && caseInfo . Fields . Length > 0 )
74129 {
130+ object [ ] fields = caseInfo . FieldReader ( value ) ;
131+
75132 writer . WritePropertyName ( ( resolver != null ) ? resolver . GetResolvedPropertyName ( FieldsPropertyName ) : FieldsPropertyName ) ;
76- serializer . Serialize ( writer , fields ) ;
133+ serializer . Serialize ( writer , fields ) ;
77134 }
78135 writer . WriteEndObject ( ) ;
79136 }
@@ -91,7 +148,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
91148 if ( reader . TokenType == JsonToken . Null )
92149 return null ;
93150
94- object matchingCaseInfo = null ;
151+ UnionCase caseInfo = null ;
95152 string caseName = null ;
96153 JArray fields = null ;
97154
@@ -105,20 +162,13 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
105162 {
106163 ReadAndAssert ( reader ) ;
107164
108- IEnumerable cases = ( IEnumerable ) FSharpUtils . GetUnionCases ( null , objectType , null ) ;
165+ Union union = UnionCache . Get ( objectType ) ;
109166
110167 caseName = reader . Value . ToString ( ) ;
111168
112- foreach ( object c in cases )
113- {
114- if ( ( string ) FSharpUtils . GetUnionCaseInfoName ( c ) == caseName )
115- {
116- matchingCaseInfo = c ;
117- break ;
118- }
119- }
169+ caseInfo = union . Cases . SingleOrDefault ( c => c . Name == caseName ) ;
120170
121- if ( matchingCaseInfo == null )
171+ if ( caseInfo == null )
122172 throw JsonSerializationException . Create ( reader , "No union type found with the name '{0}'." . FormatWith ( CultureInfo . InvariantCulture , caseName ) ) ;
123173 }
124174 else if ( string . Equals ( propertyName , FieldsPropertyName , StringComparison . OrdinalIgnoreCase ) )
@@ -137,31 +187,30 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
137187 ReadAndAssert ( reader ) ;
138188 }
139189
140- if ( matchingCaseInfo == null )
190+ if ( caseInfo == null )
141191 throw JsonSerializationException . Create ( reader , "No '{0}' property with union name found." . FormatWith ( CultureInfo . InvariantCulture , CasePropertyName ) ) ;
142-
143- PropertyInfo [ ] fieldProperties = ( PropertyInfo [ ] ) FSharpUtils . GetUnionCaseInfoFields ( matchingCaseInfo ) ;
144- object [ ] typedFieldValues = new object [ fieldProperties . Length ] ;
145192
146- if ( fieldProperties . Length > 0 && fields == null )
193+ object [ ] typedFieldValues = new object [ caseInfo . Fields . Length ] ;
194+
195+ if ( caseInfo . Fields . Length > 0 && fields == null )
147196 throw JsonSerializationException . Create ( reader , "No '{0}' property with union fields found." . FormatWith ( CultureInfo . InvariantCulture , FieldsPropertyName ) ) ;
148197
149198 if ( fields != null )
150199 {
151- if ( fieldProperties . Length != fields . Count )
200+ if ( caseInfo . Fields . Length != fields . Count )
152201 throw JsonSerializationException . Create ( reader , "The number of field values does not match the number of properties definied by union '{0}'." . FormatWith ( CultureInfo . InvariantCulture , caseName ) ) ;
153202
154203
155204 for ( int i = 0 ; i < fields . Count ; i ++ )
156205 {
157206 JToken t = fields [ i ] ;
158- PropertyInfo fieldProperty = fieldProperties [ i ] ;
207+ PropertyInfo fieldProperty = caseInfo . Fields [ i ] ;
159208
160209 typedFieldValues [ i ] = t . ToObject ( fieldProperty . PropertyType , serializer ) ;
161210 }
162211 }
163212
164- return FSharpUtils . MakeUnion ( null , matchingCaseInfo , typedFieldValues , null ) ;
213+ return caseInfo . Constructor ( typedFieldValues ) ;
165214 }
166215
167216 /// <summary>
0 commit comments