Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 29 additions & 21 deletions SW.Bitween.Api/Resources/Mappers/Preview.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,32 +47,40 @@ public async Task<MapperPreviewResponse> Handle(MapperPreviewRequest request)
{
var inputJson = request.InputJson;

JObject? jObj = null;
if (JToken.Parse(inputJson) is JObject parsedObj)
jObj = parsedObj;
var parsed = JToken.Parse(inputJson);
var jObj = parsed as JObject;
var jArr = parsed as JArray;

if (jObj != null)
{
var enriched = false;
JObject? partnerObj = partner?.AdapterProperties?.Count > 0
? JObject.FromObject(partner.AdapterProperties)
: null;

if (partner?.AdapterProperties?.Count > 0)
{
jObj["__partner__"] = JObject.FromObject(partner.AdapterProperties);
enriched = true;
}
JObject? globalsObj = null;
var nonEmptySets = globalSets.Where(s => s.Values?.Count > 0).ToList();
if (nonEmptySets.Count > 0)
{
globalsObj = new JObject();
foreach (var set in nonEmptySets)
globalsObj[set.Id] = JObject.FromObject(set.Values);
}

var nonEmptySets = globalSets.Where(s => s.Values?.Count > 0).ToList();
if (nonEmptySets.Count > 0)
if (jObj != null && (partnerObj != null || globalsObj != null))
{
if (partnerObj != null) jObj["__partner__"] = partnerObj;
if (globalsObj != null) jObj["__globals__"] = globalsObj;
inputJson = jObj.ToString(Formatting.None);
}
else if (jArr != null && (partnerObj != null || globalsObj != null))
{
foreach (var token in jArr)
{
var globalsObj = new JObject();
foreach (var set in nonEmptySets)
globalsObj[set.Id] = JObject.FromObject(set.Values);
jObj["__globals__"] = globalsObj;
enriched = true;
if (token is JObject elem)
{
if (partnerObj != null) elem["__partner__"] = partnerObj;
if (globalsObj != null) elem["__globals__"] = globalsObj;
}
}

if (enriched)
inputJson = jObj.ToString(Formatting.None);
inputJson = jArr.ToString(Formatting.None);
}

var output = ScribanJsonHelper.Render(request.ScribanTemplate, inputJson);
Expand Down
40 changes: 33 additions & 7 deletions SW.Bitween.NativeAdapters/JsonMapper/ScribanJsonHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,37 @@ public static class ScribanJsonHelper
/// </summary>`
public static string Render(string scribanTemplate, string inputJson)
{
// 1. Parse input JSON
var inputObj = JObject.Parse(inputJson);
// 1. Parse input JSON — handle both root object and root array
var rootToken = JToken.Parse(inputJson);

// 2. Build top-level ScriptObject from input (recursive)
var scriptObj = BuildScriptObject(inputObj);
ScriptObject scriptObj;
if (rootToken is JArray rootArray)
{
// Expose the array under "items" and also forward member access to the
// first element so templates can write either:
// {{ items[0].OrderId }} or {{ for item in items }} ... {{ end }}
scriptObj = new ScriptObject();
var smartArray = new SmartArray(rootArray.Select(ToScribanValue));
scriptObj["items"] = smartArray;

// If the first element is an object, also hoist its properties to the
// top level so templates that reference fields directly still work.
if (rootArray.Count > 0 && rootArray[0] is JObject firstObj)
{
var firstSo = BuildScriptObject(firstObj);
foreach (var key in firstSo.Keys.ToList())
scriptObj.TrySetValue(null!, default, key, firstSo[key], false);
}
}
else if (rootToken is JObject rootObj)
{
scriptObj = BuildScriptObject(rootObj);
}
else
{
throw new InvalidOperationException("Input JSON must be a root object or root array.");
}

// 3. Register custom functions (| json pipe, | to_float cast)
var functions = new ScriptObject();
Expand Down Expand Up @@ -50,19 +76,19 @@ public static string Render(string scribanTemplate, string inputJson)
// 6. Strip trailing commas that may appear after the last field/element
rendered = Regex.Replace(rendered, @",(\s*[}\]])", "$1");

// 7. Parse rendered output as JToken
JObject flat;
// 7. Parse rendered output — root may be an object OR an array
JToken renderedToken;
try
{
flat = JObject.Parse(rendered);
renderedToken = JToken.Parse(rendered);
}
catch (JsonException ex)
{
throw new InvalidOperationException($"Template produced invalid JSON: {ex.Message}\n\nRendered:\n{rendered}");
}

// 8. Expand dotted keys into nested objects recursively at all depths
return ExpandDottedKeys(flat).ToString(Formatting.Indented);
return ExpandDottedKeys(renderedToken).ToString(Formatting.Indented);
}

// ─── Helpers ──────────────────────────────────────────────────────────────
Expand Down
Loading