4
votes

The only way I've been able to do it is through the reflection-based code below. I can't believe there's not an "easier NetSuite way" to do this though? Am I missing something basic?

After I perform a search on custom objects I get back an array of Record[], this can then be looped through and each item casted to a CustomObject.

The properties of the custom object are stored in the CustomRecord's customFieldList but the values are not immediately accessible you have to cast those the their real NetSuite type (like LongCustomFieldRef, DoubleCustomFieldRef, BooleanCustomFieldRef, StringCustomFieldRef, etc).

In order to not have to bother with this mess to get nice clean objects on my side I decided on the approach below:

Create classes with property names that match (including case) the NetSuite names and inherits from NetSuiteBase (defined below)

public class MyNetSuiteObject : NetSuiteBase //<-- Note base class
{
    public string myProperty1 { get; set; }
    public bool myProperty2 { get; set; }
    public int myProperty3 { get; set; }

    public static MyNetSuiteObject FromCustomSearchRecord(CustomRecord customRecord)
    {
        var ret = new MyNetSuiteObject();
        ret.AssignProperties(customRecord);
        return ret;
    }
}

Create a base class which will inspect CustomRecords and apply property values to the .NET classes

public class NetSuiteBase
{

    public void AssignProperties(CustomRecord customRecord)
    {
        var classProps = this.GetType().GetProperties();
        foreach (var prop in classProps)
        {
            var propName = prop.Name;
            var propValue = prop.GetValue(this, null);

            //get the matching CustomFieldRef out of the customFieldList for the CustomRecord which matches our current property name
            var myCustomFieldRef = customRecord.customFieldList.Where(c => c.scriptId == propName).FirstOrDefault();

            if (myCustomFieldRef == null) continue;

            //we can't get the value out until we cast the CustomFieldRef to its "actual" type.
            var custType = myCustomFieldRef.GetType().Name;
            switch (custType)
            {
                case "LongCustomFieldRef":
                    TrySetProperty(prop, ((LongCustomFieldRef)myCustomFieldRef).value.ToString());
                    break;

                case "DoubleCustomFieldRef":
                    TrySetProperty(prop, ((DoubleCustomFieldRef)myCustomFieldRef).value.ToString());
                    break;

                case "BooleanCustomFieldRef":
                    TrySetProperty(prop, ((BooleanCustomFieldRef)myCustomFieldRef).value.ToString());
                    break;

                case "StringCustomFieldRef":
                    TrySetProperty(prop, ((StringCustomFieldRef)myCustomFieldRef).value.ToString());
                    break;

                case "DateCustomFieldRef":
                    TrySetProperty(prop, ((DateCustomFieldRef)myCustomFieldRef).value.ToString());
                    break;

                case "SelectCustomFieldRef":
                    TrySetProperty(prop, ((SelectCustomFieldRef)myCustomFieldRef).value.name.ToString());
                    break;

                case "MultiSelectCustomFieldRef":
                    TrySetProperty(prop, ((MultiSelectCustomFieldRef)myCustomFieldRef).value.ToString());
                    break;
                default:
                    Console.WriteLine("Unknown type: " + myCustomFieldRef.internalId);
                    break;
            }
        }
    }


    //Some of the NetSuite properties are represented as strings (I'm looking at you BOOLs), so we pass all the values from above
    //as strings and them process/attempt casts
    private void TrySetProperty(PropertyInfo prop, string value)
    {
        value = value.ToLower().Trim();

        if (prop.PropertyType == typeof(string))
        {
            prop.SetValue(this, value);
            return;
        }


        if (prop.PropertyType == typeof(bool))
        {
            if (value == "yes") value = "true";
            if (value == "no") value = "false";
            if (value == "1") value = "true";
            if (value == "0") value = "false";

            bool test;
            if (bool.TryParse(value, out test))
            {
                prop.SetValue(this, test);
                return;
            }
        }

        if (prop.PropertyType == typeof(int))
        {
            int test;
            if (int.TryParse(value, out test))
            {
                prop.SetValue(this, test);
                return;
            }
        }

        if (prop.PropertyType == typeof(double))
        {
            double test;
            if (double.TryParse(value, out test))
            {
                prop.SetValue(this, test);
                return;
            }
        }

        if (prop.PropertyType == typeof(decimal))
        {
            decimal test;
            if (decimal.TryParse(value, out test))
            {
                prop.SetValue(this, test);
                return;
            }
        }

    }
}

After performing a NetSuite search on custom objects, loop through the results and use the above classes to convert NetSuite result to a .NET class

for (int i = 0, j = 0; i < records.Length; i++, j++)
{
    customRecord = (CustomRecord)records[i];

    var myNetSuiteObject = MyNetSuiteObject.FromCustomSearchRecord(customRecord);
}

Is there some other "NetSuite way" to accomplish what I have above?

1

1 Answers

0
votes

No, there is no "NetSuite" way. What you've done is far above and beyond the call of duty, if you ask me, and it's amazing. The NetSuite/SuiteTalk WSDL is just HORRENDOUS, and so many bad choices were made in the design process that I'm shocked that it was released as-is to developers without any questions raised. Below is one other example.

Bad API construction

This is from the documentation for their SuiteTalk course, which reveals that when parsing the results of an Advanced search, they contain within them SearchRowBasic objects. Well within each SearchRowBasic object there are multiple fields. To access those fields' values, you have to take the first element of a SearchColumnField array. Why would you DO this? If there will only ever be ONE value (and they state in their documentation that yes indeed there will only ever be ONE value), WHY the heck would you make the return object an array instead of just passing back to the developer the value of the primitive type itself directly??? That's just plain bad API construction right there, forcing the developers to use SearchColumnField[0].searchValue every time rather than just SearchColumnField.searchValue!