1+ using System . Collections . Generic ;
2+ using System . Linq ;
3+ using Newtonsoft . Json . Linq ;
4+
5+ namespace HandlebarsDotNet . Helpers ;
6+
7+ internal static class JArrayMerger
8+ {
9+ public static JArray MergeToCommonStructure ( JArray originalArray )
10+ {
11+ if ( originalArray . Count is 0 or 1 )
12+ {
13+ // If array is empty, or has only one item, return as-is
14+ return originalArray ;
15+ }
16+
17+ // Create a merged template from all items
18+ var mergedTemplate = CreateMergedTemplate ( originalArray ) ;
19+ if ( mergedTemplate == null )
20+ {
21+ return originalArray ;
22+ }
23+
24+ // Apply the merged template to all items
25+ var mergedItems = new List < JToken > ( ) ;
26+ foreach ( var item in originalArray )
27+ {
28+ var mergedItem = ApplyTemplate ( item , mergedTemplate ) ;
29+ mergedItems . Add ( mergedItem ) ;
30+ }
31+
32+ var newArray = new JArray ( mergedItems ) ;
33+
34+ // Check if the new array is the same as the original
35+ if ( JToken . DeepEquals ( originalArray , newArray ) )
36+ {
37+ return originalArray ;
38+ }
39+
40+ return newArray ;
41+ }
42+
43+ private static JToken ? CreateMergedTemplate ( JArray array )
44+ {
45+ if ( array . Count == 0 )
46+ {
47+ return null ;
48+ }
49+
50+ // Start with the first item as base template
51+ var template = array [ 0 ] . DeepClone ( ) ;
52+
53+ // Merge each subsequent item into the template
54+ for ( var i = 1 ; i < array . Count ; i ++ )
55+ {
56+ template = MergeTokens ( template , array [ i ] ) ;
57+ if ( template == null )
58+ {
59+ return null ; // Cannot merge - incompatible types
60+ }
61+ }
62+
63+ return template ;
64+ }
65+
66+ private static JToken ? MergeTokens ( JToken template , JToken item )
67+ {
68+ // If types don't match, merging is not possible
69+ if ( template . Type != item . Type )
70+ {
71+ return null ;
72+ }
73+
74+ return template . Type switch
75+ {
76+ JTokenType . Object => MergeObjects ( ( JObject ) template , ( JObject ) item ) ,
77+ JTokenType . Array => MergeArrays ( ( JArray ) template , ( JArray ) item ) ,
78+ _ => template
79+ } ;
80+ }
81+
82+ private static JObject MergeObjects ( JObject template , JObject item )
83+ {
84+ var result = ( JObject ) template . DeepClone ( ) ;
85+
86+ // Add all properties from item that don't exist in template
87+ foreach ( var property in item . Properties ( ) )
88+ {
89+ if ( result . Property ( property . Name ) == null )
90+ {
91+ result [ property . Name ] = property . Value . DeepClone ( ) ;
92+ }
93+ else
94+ {
95+ // Property exists in both - try to merge their values
96+ var mergedValue = MergeTokens ( result [ property . Name ] ! , property . Value ) ;
97+ if ( mergedValue != null )
98+ {
99+ result [ property . Name ] = mergedValue ;
100+ }
101+ }
102+ }
103+
104+ return result ;
105+ }
106+
107+ private static JArray MergeArrays ( JArray template , JArray _ )
108+ {
109+ return ( JArray ) template . DeepClone ( ) ;
110+ }
111+
112+ private static JToken ApplyTemplate ( JToken item , JToken template )
113+ {
114+ if ( template . Type != item . Type )
115+ {
116+ return item ; // Cannot apply template to different type
117+ }
118+
119+ return template . Type switch
120+ {
121+ JTokenType . Object => ApplyObjectTemplate ( ( JObject ) item , ( JObject ) template ) ,
122+ JTokenType . Array => ApplyArrayTemplate ( ( JArray ) item , ( JArray ) template ) ,
123+ _ => item
124+ } ;
125+ }
126+
127+ private static JObject ApplyObjectTemplate ( JObject item , JObject template )
128+ {
129+ var result = new JObject ( ) ;
130+
131+ // Add all properties from template
132+ foreach ( var templateProp in template . Properties ( ) )
133+ {
134+ var itemProp = item . Property ( templateProp . Name ) ;
135+ if ( itemProp != null )
136+ {
137+ // Property exists in item - apply template recursively
138+ result [ templateProp . Name ] = ApplyTemplate ( itemProp . Value , templateProp . Value ) ;
139+ }
140+ else
141+ {
142+ // Property doesn't exist in item - add default value
143+ result [ templateProp . Name ] = CreateDefaultValue ( templateProp . Value ) ;
144+ }
145+ }
146+
147+ return result ;
148+ }
149+
150+ private static JArray ApplyArrayTemplate ( JArray item , JArray _ )
151+ {
152+ // For arrays, return the original item array
153+ // This could be enhanced to apply template to array elements
154+ return item ;
155+ }
156+
157+ private static JToken CreateDefaultValue ( JToken templateValue )
158+ {
159+ return templateValue . Type switch
160+ {
161+ JTokenType . String => new JValue ( string . Empty ) ,
162+ JTokenType . Integer => new JValue ( 0 ) ,
163+ JTokenType . Float => new JValue ( 0.0 ) ,
164+ JTokenType . Boolean => new JValue ( false ) ,
165+ JTokenType . Array => CreateEmptyArrayFromTemplate ( ( JArray ) templateValue ) ,
166+ JTokenType . Object => CreateEmptyObjectFromTemplate ( ( JObject ) templateValue ) ,
167+ JTokenType . Null => JValue . CreateNull ( ) ,
168+ _ => JValue . CreateNull ( )
169+ } ;
170+ }
171+
172+ private static JArray CreateEmptyArrayFromTemplate ( JArray templateArray )
173+ {
174+ var result = new JArray ( ) ;
175+
176+ // If the template array has elements, create default instances of the same types
177+ if ( templateArray . Count > 0 )
178+ {
179+ // Get the unique element types from the template array
180+ var elementTypes = templateArray
181+ . Select ( element => element . Type )
182+ . Distinct ( ) ;
183+
184+ // Create default instances for each unique type found in the template
185+ foreach ( var elementType in elementTypes )
186+ {
187+ // Find a representative element of this type from the template
188+ var templateElement = templateArray . First ( e => e . Type == elementType ) ;
189+
190+ // Create a default value based on this template element
191+ var defaultElement = CreateDefaultValue ( templateElement ) ;
192+ result . Add ( defaultElement ) ;
193+ }
194+ }
195+
196+ return result ;
197+ }
198+
199+ private static JObject CreateEmptyObjectFromTemplate ( JObject templateObject )
200+ {
201+ var result = new JObject ( ) ;
202+
203+ // Create an object with the same property structure but with default values
204+ foreach ( var property in templateObject . Properties ( ) )
205+ {
206+ result [ property . Name ] = CreateDefaultValue ( property . Value ) ;
207+ }
208+
209+ return result ;
210+ }
211+ }
0 commit comments