I have been tasked with interfacing with a third-party software to manage invoice submittal and payment. It’s all done via ftp and I have the server setup to handle the ftp requests outside of Epicor.
I created the query within the Epicor space with the thought to have the Epicor server perform the query and return the results to me via REST. This is working fantastically but I’m struggling to turn this into JSON. Each time the call returns a blob string which I’ve tried a few different avenues to ‘prettify’ it.
It kept throwing errors about non-nullable properties if I don’t use the null-conditional operator. Could somebody explain that too possibly?
My thought is to call the API, iterate through the dataset and I have to create a pipe-delimited text file from a few different tables, once every 2 hours and upload to the ftp server. I have the ftp script written and uploading files correctly, it’s just getting this data into a format that I know what to do with.
Might take me a second to parse what you are doing exactly, but if you want to “prettify” the json you are getting back you can just use Newtonsoft.Json, and do this:
//ref Newtonsoft.Json
using Newtonsoft.Json;
string pretty = JToken.Parse(BAQResults).ToString();
I just attempted that and got it to prettify but it cuts out over half of my dataset for some reason. I’m pulling two companies worth of data. The results that I get when it IS formatted correctly cuts out all Vendors of one company and picks of in the letter ‘N’ in the second company. Its super weird because it’s not even the start of that Vendor, it’s half of the object too. It’s like a character is in the string that’s throwing it off somehow but there isn’t anything crazy in the raw data and Postman display the results with no problem.
I come from a JavaScript background and my mind thought about turning it into JSON and iterating through the dataset that way. Am I thinking about it wrong? Would it be better to keep it as a string? How do I iterate through that?
Got it. That’s what was confusing me along with I believe I hit a buffer limit on the console. I put an incrementor on a loop while it printed and saw that even though it was half of an object, the count started at row 5,xxx. I created it as a file and saw it.
I know @josecgomez recommended an easier way which i’ll totally take up if I just can’t get this but I’m kind of determine to understand this and finish it this way
I jumped back into this after getting home. I’m getting stuck at the @odata.context portion of the JSON string. How do I map this via deserialization? That’s not a valid variable name. Would creating a class of oData and adding a field for context that holds a string value then make the oData variable of the oData class?
namespace Discard1;
using System.IO;
using Newtonsoft.Json;
public class EpiJSON
{
public List<ABCCode> value;
}
public class ABCCode
{
public string? ABCCode_Company;
public string? ABCCode_ABCCode;
public int? ABCCode_CountFreq;
public bool? ABCCode_ExcludeFromCC;
public string? ABCCode_StockValPcnt;
public string? ABCCode_PcntTolerance;
public bool? ABCCode_CalcPcnt;
public bool? ABCCode_CalcQty;
public bool? ABCCode_CalcValue;
public string? ABCCode_QtyTolerance;
public string? ABCCode_ValueTolerance;
public int? ABCCode_ShipToCustNum;
public Guid? ABCCode_SysRowID;
public Guid? RowIdent;
}
class Program
{
static void Main(string[] args)
{
string json = File.ReadAllText("abc.json");
var anObject = JsonConvert.DeserializeObject<EpiJSON>(json);
foreach(var abc in anObject.value)
{
Console.WriteLine(abc.ABCCode_SysRowID.ToString());
}
}
}
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;
var anObject = JsonConvert.DeserializeObject<EpiJSON>(json, jsonSerializerSettings);
Gets or sets how missing members (e.g. JSON contains a property that isn’t a member on the object) are handled during deserialization. The default value is Ignore.
You don’t need to define contracts to use deserialized objects, just use dynamic late binding… This is how I run BAQs from C#. Don’t use the oData (just V2 custom methods) because sometimes I need to pass parameter to BAQs that would make the url too long.
var baseAddress = $"{Session.AppServerURL}api/v2/odata/{Session.CompanyID}";
try
{
using (var client = new HttpClient())
{
// Get the UBAQ parameters dataset
var queryIdParamJson = JsonConvert.SerializeObject(new { queryID = baqId });
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(baseAddress + "/Ice.BO.DynamicQuerySvc/GetQueryExecutionParametersByID"),
Headers =
{
{ "Authorization", "Basic " + auth },
{ "Accept", "application/json" },
{ "X-API-Key", apiKey },
},
Content = new StringContent(queryIdParamJson, Encoding.UTF8, "application/json")
};
var result = client.SendAsync(request).Result;
if (result.IsSuccessStatusCode)
{
dynamic jsonRsp = JsonConvert.DeserializeObject(result.Content.ReadAsStringAsync().Result);
var ds = jsonRsp.returnObj;
// Set UBAQ parameters
foreach (var param in ds.ExecutionParameter)
{
if (param.ParameterID == "param1")
{
param.ParameterValue = param1Value;
param.IsEmpty = false;
}
else if (param.ParameterID == "param2")
{
param.ParameterValue = param2Value;
param.IsEmpty = false;
}
}
var queryParamsJson = JsonConvert.SerializeObject(new { queryID = baqId, executionParams = ds });
request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(baseAddress + "/Ice.BO.DynamicQuerySvc/ExecuteByID"),
Headers =
{
{ "Authorization", "Basic " + auth },
{ "Accept", "application/json" },
{ "X-API-Key", apiKey },
},
Content = new StringContent(queryParamsJson, Encoding.UTF8, "application/json")
};
// Call UBAQ
result = client.SendAsync(request).Result;
if (result.IsSuccessStatusCode)
{
jsonRsp = JsonConvert.DeserializeObject(result.Content.ReadAsStringAsync().Result);
// Get the returned dataset as a dynamic
ds = jsonRsp.returnObj;
foreach (var row in ds.Results)
{
// Do something
var a = (decimal)row.Calculated_SomeCalcField;
}
}
}
}
}
catch (Exception ex)
{
// Handle exceptions
}
If you need to map the results to an internal type for your application, just do it in the final foreach.