Skip to content

Commit a63f549

Browse files
author
aafent
committed
Json library, New Packages versions (Interpreter,Toolkit)
1 parent 08296ad commit a63f549

14 files changed

Lines changed: 563 additions & 25 deletions

File tree

FAST.FBasic.InteractiveConsole/FBasicIC_Setup.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
using FAST.FBasic.TemplatingLibrary;
1+
using FAST.FBasic.InteractiveConsole.DemoObjects;
2+
using FAST.FBasic.TemplatingLibrary;
23
using FAST.FBasicInteractiveConsole.TestCode;
34
using FAST.FBasicInterpreter;
5+
using FAST.FBasicInterpreter.Types;
46
using Microsoft.Data.Sqlite;
7+
using System.Dynamic;
8+
using System.Text.Json;
59

610
namespace FAST.FBasic.InteractiveConsole
711
{
@@ -39,6 +43,11 @@ private void setupEnvironment()
3943
return true;
4044
}
4145

46+
if (request.Context=="JSON.DESERIALIZE")
47+
{ // Context:JSON.DESERIALIZE, Group:text, Name:emp
48+
var json = request.Interpreter.GetVar(request.Group).String;
49+
return JsonSerializer.Deserialize(json,typeof(EmployeeProfile) );
50+
}
4251

4352
if (request.Level3Request() == "IN.OBJECT.EMPLOYPROFILE")
4453
{
@@ -47,7 +56,13 @@ private void setupEnvironment()
4756

4857
if (request.Level3Request() == "IN.OBJECT.SHOWEMPLOYPROFILE")
4958
{
50-
return new DemoObjects.EmployeeProfileExample().GetEmployeeProfile();
59+
var emp=request.Interpreter.GetVar(request.VariableName).Object;
60+
if (emp is ExpandoObject)
61+
{
62+
emp = ExpandoMapper.MapToType<EmployeeProfile>((ExpandoObject)emp);
63+
}
64+
new DemoObjects.EmployeeProfileExample().TestPrint( (EmployeeProfile)emp);
65+
return emp;
5166
}
5267

5368

FAST.FBasic.InteractiveConsole/Tests/test.bas renamed to FAST.FBasic.InteractiveConsole/Tests/json1.bas

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@ REM
33
REM (v) request for an object that will be used into this program
44
rinput OBJECT,EMPLOYPROFILE, emp ' load the external object into variable: emp
55

6-
json text FROM emp
7-
json text2 dynamic from emp
86

9-
print emp
7+
json text FROM emp
8+
print text
9+
json text to emp
10+
rinput OBJECT,SHOWEMPLOYPROFILE,emp 'show the object
1011
print ""
1112
print ""
1213
print ""
13-
print text
14+
15+
json text2 dynamic FROM emp
16+
print text2
1417
print ""
1518
print ""
16-
print text2
19+
json text2 DYNAMIC to emp
20+
21+
rinput OBJECT,SHOWEMPLOYPROFILE,emp 'show the object
1722

1823
Halt

FAST.FBasic.LibraryToolkit/Core/Errors.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
using System.ComponentModel.Design;
2+
using System.Reflection.Metadata;
23

34
namespace FAST.FBasicInterpreter
45
{
56
public static class Errors
67
{
8+
9+
public static string E100_RequestForObjectHandlerNotInstalled(IFBasicRequestForObjectDescriptor request =null, string more="")
10+
{
11+
if (request != null)
12+
{
13+
more=$" ({request.Context}, {request.Group}, {request.Name}) {more}";
14+
}
15+
return $"The Request For Object Handler is not installed. {more} [E100].";
16+
}
17+
18+
719
public static string E102_ExpectingThatButGotThis(string expected, string found)
820
{
921
return $"Expecting {expected}, got {found} [E102]";

FAST.FBasic.LibraryToolkit/FAST.FBasic.LibraryToolkit.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<Copyright>Global Cloud Services EOOD</Copyright>
1111
<PackageOutputPath>.\Packages</PackageOutputPath>
1212
<PackageId>FAST.FBasicLibraryToolkit</PackageId>
13-
<Version>1.0.0</Version>
13+
<Version>1.0.1</Version>
1414
<Authors>GCS</Authors>
1515
<RepositoryUrl>https://github.com/aafent/FAST.FBasic</RepositoryUrl>
1616
<PackageProjectUrl>https://github.com/aafent/FAST.FBasic/blob/main/README.md</PackageProjectUrl>
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
using System.Collections;
2+
using System.Dynamic;
3+
using System.Reflection;
4+
using System.Text.Json;
5+
6+
namespace FAST.FBasicInterpreter.Types
7+
{
8+
/// <summary>
9+
/// Key Features:
10+
/// Generic method MapToType<T>() and non-generic MapToType() for flexibility
11+
/// Deep cloning - recursively maps nested objects and collections at any depth
12+
/// Array support - handles arrays of any type including nested objects
13+
/// Collection support - works with List<T>, IList<T>, ICollection<T>, IEnumerable<T>
14+
/// Record support - properly handles C# records with primary constructors and init-only properties
15+
/// Case-insensitive matching - property names are matched regardless of casing
16+
/// Graceful bypassing - ignores properties that don't exist or don't match in either the source or target
17+
/// Type conversion - automatically converts compatible types (primitives, DateTime, Guid, etc.)
18+
///
19+
/// How it works:
20+
/// Creates an instance of the target type (handles parameterless and parameterized constructors)
21+
/// Iterates through all writable properties
22+
/// Matches properties by name (case-insensitive)
23+
/// Recursively maps nested ExpandoObjects, arrays, and collections
24+
/// Silently skips properties that can't be mapped
25+
/// The mapper handles complex scenarios like records with primary constructors, nested objects at any depth, and mixed collections of primitives and complex types.
26+
/// </summary>
27+
public static class ExpandoMapper
28+
{
29+
/// <summary>
30+
/// Maps an ExpandoObject to a specified type or record, copying matching properties recursively.
31+
/// </summary>
32+
/// <typeparam name="T">The target type to map to</typeparam>
33+
/// <param name="expandoObject">The source ExpandoObject</param>
34+
/// <returns>An instance of T with mapped properties</returns>
35+
public static T MapToType<T>(ExpandoObject expandoObject)
36+
{
37+
return (T)MapToType(expandoObject, typeof(T));
38+
}
39+
40+
/// <summary>
41+
/// Maps an ExpandoObject to a specified type or record, copying matching properties recursively.
42+
/// </summary>
43+
/// <param name="expandoObject">The source ExpandoObject</param>
44+
/// <param name="targetType">The target type to map to</param>
45+
/// <returns>An instance of the target type with mapped properties</returns>
46+
public static object MapToType(ExpandoObject expandoObject, Type targetType)
47+
{
48+
if (expandoObject == null)
49+
return null!;
50+
51+
// Get the dictionary representation of the ExpandoObject
52+
var expandoDict = (IDictionary<string, object>)expandoObject;
53+
54+
// Create an instance of the target type
55+
object instance = CreateInstance(targetType, expandoDict);
56+
57+
// Get all writable properties of the target type
58+
var properties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
59+
.Where(p => p.CanWrite || IsInitOnlyProperty(p));
60+
61+
foreach (var property in properties)
62+
{
63+
// Try to find a matching key in the expando object (case-insensitive)
64+
var matchingKey = expandoDict.Keys.FirstOrDefault(k =>
65+
string.Equals(k, property.Name, StringComparison.OrdinalIgnoreCase));
66+
67+
if (matchingKey == null)
68+
continue;
69+
70+
var expandoValue = expandoDict[matchingKey];
71+
72+
if (expandoValue == null)
73+
{
74+
SetPropertyValue(instance, property, null);
75+
continue;
76+
}
77+
78+
try
79+
{
80+
var mappedValue = MapValue(expandoValue, property.PropertyType);
81+
SetPropertyValue(instance, property, mappedValue);
82+
}
83+
catch
84+
{
85+
// Bypass if mapping fails
86+
continue;
87+
}
88+
}
89+
90+
return instance;
91+
}
92+
93+
94+
private static object MapValue(object value, Type targetType)
95+
{
96+
if (value == null)
97+
return null;
98+
99+
100+
if (value is JsonElement)
101+
{
102+
value = JsonElementConverter.ConvertToPlainValue((JsonElement)value);
103+
}
104+
105+
var valueType = value.GetType();
106+
107+
// Handle nullable types
108+
var underlyingType = Nullable.GetUnderlyingType(targetType);
109+
if (underlyingType != null)
110+
targetType = underlyingType;
111+
112+
// Direct assignment for matching types or convertible types
113+
if (targetType.IsAssignableFrom(valueType))
114+
return value;
115+
116+
// Handle primitive types and strings
117+
if (targetType.IsPrimitive || targetType == typeof(string) || targetType == typeof(decimal) ||
118+
targetType == typeof(DateTime) || targetType == typeof(Guid))
119+
{
120+
return Convert.ChangeType(value, targetType);
121+
}
122+
123+
// Handle arrays
124+
if (targetType.IsArray)
125+
{
126+
return MapArray(value, targetType);
127+
}
128+
129+
// Handle generic lists and collections
130+
if (targetType.IsGenericType)
131+
{
132+
var genericTypeDef = targetType.GetGenericTypeDefinition();
133+
134+
if (genericTypeDef == typeof(List<>) ||
135+
genericTypeDef == typeof(IList<>) ||
136+
genericTypeDef == typeof(ICollection<>) ||
137+
genericTypeDef == typeof(IEnumerable<>))
138+
{
139+
return MapList(value, targetType);
140+
}
141+
}
142+
143+
// Handle nested ExpandoObjects
144+
if (value is ExpandoObject expandoValue)
145+
{
146+
return MapToType(expandoValue, targetType);
147+
}
148+
149+
// Handle dictionaries as ExpandoObjects
150+
if (value is IDictionary<string, object> dict)
151+
{
152+
var expando = new ExpandoObject();
153+
var expandoDict = (IDictionary<string, object>)expando;
154+
foreach (var kvp in dict)
155+
{
156+
expandoDict[kvp.Key] = kvp.Value;
157+
}
158+
return MapToType(expando, targetType);
159+
}
160+
161+
// Try to convert as a last resort
162+
return Convert.ChangeType(value, targetType);
163+
}
164+
165+
private static object MapArray(object value, Type targetType)
166+
{
167+
var elementType = targetType.GetElementType();
168+
169+
if (value is IEnumerable enumerable)
170+
{
171+
var items = enumerable.Cast<object>().ToList();
172+
var array = Array.CreateInstance(elementType, items.Count);
173+
174+
for (int i = 0; i < items.Count; i++)
175+
{
176+
var mappedItem = MapValue(items[i], elementType);
177+
array.SetValue(mappedItem, i);
178+
}
179+
180+
return array;
181+
}
182+
183+
throw new InvalidOperationException("Cannot map non-enumerable to array");
184+
}
185+
186+
private static object MapList(object value, Type targetType)
187+
{
188+
var elementType = targetType.GetGenericArguments()[0];
189+
var listType = typeof(List<>).MakeGenericType(elementType);
190+
var list = (IList)Activator.CreateInstance(listType);
191+
192+
if (value is IEnumerable enumerable)
193+
{
194+
foreach (var item in enumerable)
195+
{
196+
var mappedItem = MapValue(item, elementType);
197+
list.Add(mappedItem);
198+
}
199+
}
200+
201+
return list;
202+
}
203+
204+
private static object CreateInstance(Type type, IDictionary<string, object> expandoDict)
205+
{
206+
// For records and classes with primary constructors, try to match constructor parameters
207+
var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
208+
.OrderByDescending(c => c.GetParameters().Length);
209+
210+
foreach (var constructor in constructors)
211+
{
212+
var parameters = constructor.GetParameters();
213+
214+
if (parameters.Length == 0)
215+
{
216+
return Activator.CreateInstance(type);
217+
}
218+
219+
// Try to match all constructor parameters with expando properties
220+
var paramValues = new object[parameters.Length];
221+
var allMatched = true;
222+
223+
for (int i = 0; i < parameters.Length; i++)
224+
{
225+
var param = parameters[i];
226+
var matchingKey = expandoDict.Keys.FirstOrDefault(k =>
227+
string.Equals(k, param.Name, StringComparison.OrdinalIgnoreCase));
228+
229+
if (matchingKey != null)
230+
{
231+
try
232+
{
233+
paramValues[i] = MapValue(expandoDict[matchingKey], param.ParameterType);
234+
}
235+
catch
236+
{
237+
paramValues[i] = GetDefault(param.ParameterType);
238+
}
239+
}
240+
else
241+
{
242+
paramValues[i] = GetDefault(param.ParameterType);
243+
}
244+
}
245+
246+
try
247+
{
248+
return Activator.CreateInstance(type, paramValues);
249+
}
250+
catch
251+
{
252+
continue;
253+
}
254+
}
255+
256+
// Fallback to parameterless constructor
257+
return Activator.CreateInstance(type);
258+
}
259+
260+
private static void SetPropertyValue(object instance, PropertyInfo property, object value)
261+
{
262+
if (property.CanWrite)
263+
{
264+
property.SetValue(instance, value);
265+
}
266+
else if (IsInitOnlyProperty(property))
267+
{
268+
// Handle init-only properties (records)
269+
property.SetValue(instance, value);
270+
}
271+
}
272+
273+
private static bool IsInitOnlyProperty(PropertyInfo property)
274+
{
275+
return property.SetMethod?.ReturnParameter
276+
?.GetRequiredCustomModifiers()
277+
?.Any(t => t.Name == "IsExternalInit") ?? false;
278+
}
279+
280+
private static object GetDefault(Type type)
281+
{
282+
return type.IsValueType ? Activator.CreateInstance(type) : null;
283+
}
284+
}
285+
}
File renamed without changes.

FAST.FBasic.LibraryToolkit/Core/FBasicArray_ToolKitExtensions.cs renamed to FAST.FBasic.LibraryToolkit/Types/FBasicArray_ToolKitExtensions.cs

File renamed without changes.

0 commit comments

Comments
 (0)