Skip to content

Commit 473acd5

Browse files
authored
Support incompatible types in array (merge to common structure) (#128)
* Support incompatible types in array (merge to common structure) * r * m
1 parent 1963607 commit 473acd5

File tree

5 files changed

+445
-2
lines changed

5 files changed

+445
-2
lines changed

src/Handlebars.Net.Helpers.DynamicLinq/Handlebars.Net.Helpers.DynamicLinq.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</PropertyGroup>
1313

1414
<ItemGroup>
15-
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.0.1" />
15+
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.5" />
1616
</ItemGroup>
1717

1818
<ItemGroup>
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
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+
}

src/Handlebars.Net.Helpers.DynamicLinq/JObjectExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,10 @@ private static IEnumerable ConvertJTokenArray(JToken arg, DynamicJsonClassOption
159159
throw new InvalidOperationException($"Unable to convert {nameof(JToken)} of type: {arg.Type} to {nameof(JArray)}.");
160160
}
161161

162+
var merged = JArrayMerger.MergeToCommonStructure(array);
163+
162164
var result = new List<object?>();
163-
foreach (var item in array)
165+
foreach (var item in merged)
164166
{
165167
result.Add(ConvertJObject(item));
166168
}

0 commit comments

Comments
 (0)