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
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ csharp_style_namespace_declarations = block_scoped:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent

[APIMatic.Core/Utilities/CoreHelper.cs]
# Disables S3011 rule (Reflection should not be used to increase accessibility)
dotnet_diagnostic.S3011.severity = none

[*.vb]
###############################
# VB Coding Conventions #
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using Newtonsoft.Json;

namespace APIMatic.Core.Test.MockTypes.Models
{
public class SimpleModelWithAdditionalPropertiesBaseModel : AdditionalPropertiesBaseModel
{
/// <summary>
/// Initializes a new instance of the <see cref="SimpleModelWithAdditionalPropertiesBaseModel"/> class.
/// </summary>
/// <param name="requiredProperty">requiredProperty.</param>
public SimpleModelWithAdditionalPropertiesBaseModel(
string requiredProperty)
{
this.RequiredProperty = requiredProperty;
}

/// <summary>
/// The required property
/// </summary>
[JsonProperty("requiredProperty")]
public string RequiredProperty { get; set; }
}

/// <summary>
/// BaseModel.
/// </summary>
public class AdditionalPropertiesBaseModel
{
/// <summary>
/// Gets or sets a dictionary holding all the additional properties.
/// </summary>
[JsonExtensionData]
public Dictionary<string, object> AdditionalProperties { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace APIMatic.Core.Test.MockTypes.Models
{
public class SimpleModelWithAdditionalPropertiesField
{
[JsonExtensionData]
private readonly IDictionary<string, JToken> _additionalProperties;

/// <summary>
/// Set the value associated with the specified key in the AdditionalProperties dictionary.
/// </summary>
[IndexerName("AdditionalPropertiesIndexer")]
public string this[string key]
{
set => _additionalProperties.SetValue(key, value);
}

/// <summary>
/// Initializes a new instance of the <see cref="SimpleModelWithAdditionalPropertiesBaseModel"/> class.
/// </summary>
/// <param name="requiredProperty">requiredProperty.</param>
public SimpleModelWithAdditionalPropertiesField(
string requiredProperty)
{
this._additionalProperties = new Dictionary<string, JToken>();
this.RequiredProperty = requiredProperty;
}

/// <summary>
/// The required property
/// </summary>
[JsonProperty("requiredProperty")]
public string RequiredProperty { get; set; }
}

internal static class AdditionalPropertiesExtensions
{
internal static void SetValue(this IDictionary<string, JToken> additionalProperties, string key, object value)
{
additionalProperties[key] = JToken.FromObject(value);
}
}
}
54 changes: 54 additions & 0 deletions APIMatic.Core.Test/Utilities/CoreHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using APIMatic.Core.Http.Configuration;
using APIMatic.Core.Test.MockTypes.Models;
Expand Down Expand Up @@ -961,6 +962,59 @@ public void PrepareFormFieldsFromObject_IndexedCoreJsonValueParameter()
List<KeyValuePair<string, object>> actual = CoreHelper.PrepareFormFieldsFromObject("jsonValue", jsonValue, ArraySerialization.Indexed);
Assert.AreEqual(expected, actual);
}

[Test]
public void PrepareFormFieldsFromObject_WithAdditionalPropertiesAsField()
{
var queryBuilder = new StringBuilder();
queryBuilder.Append(SERVER_URL);
var simpleModelWithAdditionalPropertiesField =
new SimpleModelWithAdditionalPropertiesField("Required Field")
{
["additionalPropertyKey"] = "additionalPropertyValue"
};

List<KeyValuePair<string, object>> expected = new List<KeyValuePair<string, object>>()
{
new("simpleModelWithAdditionalPropertiesField[requiredProperty]", "Required Field"),
new("simpleModelWithAdditionalPropertiesField[additionalPropertyKey]", "additionalPropertyValue")
};

List<KeyValuePair<string, object>> actual = CoreHelper.PrepareFormFieldsFromObject(
"simpleModelWithAdditionalPropertiesField", simpleModelWithAdditionalPropertiesField,
ArraySerialization.Indexed);

Assert.AreEqual(expected, actual);
}

[Test]
public void PrepareFormFieldsFromObject_WithAdditionalPropertiesBaseModel()
{
var queryBuilder = new StringBuilder();
queryBuilder.Append(SERVER_URL);
var simpleModelWithAdditionalPropertiesBaseModel =
new SimpleModelWithAdditionalPropertiesBaseModel("Required Field")
{
AdditionalProperties =
new Dictionary<string, object> { { "additionalPropertyKey", "additionalPropertyValue" } }
};

List<KeyValuePair<string, object>> expected = new List<KeyValuePair<string, object>>()
{
new("simpleModelWithAdditionalPropertiesBaseModel[requiredProperty]", "Required Field"),
new("simpleModelWithAdditionalPropertiesBaseModel[additionalPropertyKey]", "additionalPropertyValue")
};

List<KeyValuePair<string, object>> actual = CoreHelper.PrepareFormFieldsFromObject(
"simpleModelWithAdditionalPropertiesBaseModel", simpleModelWithAdditionalPropertiesBaseModel,
ArraySerialization.Indexed);

// Assert that all items in 'expected' are found within 'actual'
bool containsAllExpectedEntries = expected.All(item => actual.Contains(item));
Assert.IsTrue(containsAllExpectedEntries, "Not all expected entries are present in actual.");

}

#endregion

#region DeepCloneObject
Expand Down
51 changes: 49 additions & 2 deletions APIMatic.Core/Utilities/CoreHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,24 +280,71 @@ private static string GetConvertedValue(object value, PropertyInfo propInfo)
return convertedValue;
}

private static void PrepareFormFieldsForCustomTypes(string name, object value, ArraySerialization arraySerializationFormat, List<KeyValuePair<string, object>> keys)
private static void PrepareFormFieldsForCustomTypes(string name, object value,
ArraySerialization arraySerializationFormat, List<KeyValuePair<string, object>> keys)
{
// Custom object Iterate through its properties
var enumerator = value.GetType().GetProperties().GetEnumerator();
var t = new JsonPropertyAttribute().GetType();
while (enumerator.MoveNext())
{
var pInfo = enumerator.Current as PropertyInfo;
if (pInfo?.GetIndexParameters().Length != 0) { continue; }

var jsonProperty = (JsonPropertyAttribute)pInfo.GetCustomAttributes(t, true).FirstOrDefault();

var subName = (jsonProperty != null) ? jsonProperty.PropertyName : pInfo.Name;
string fullSubName = string.IsNullOrWhiteSpace(name) ? subName : name + '[' + subName + ']';
var subValue = pInfo.GetValue(value, null);
PrepareFormFieldsFromObject(fullSubName, subValue, arraySerializationFormat, keys, pInfo);
}

ProcessAdditionalProperties(name, value, arraySerializationFormat, keys);
}

private static void ProcessAdditionalProperties(
string name, object value, ArraySerialization arraySerializationFormat,
List<KeyValuePair<string, object>> keys)
{
// Find a member (field or property) with the [JsonExtensionData] attribute.
var additionalPropertiesMember = value.GetType()
.GetMembers(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.FirstOrDefault(member => member.GetCustomAttribute<JsonExtensionDataAttribute>() != null);

if (additionalPropertiesMember == null)
{
return;
}

object additionalProperties = additionalPropertiesMember is FieldInfo fieldInfo
? fieldInfo.GetValue(value)
: (additionalPropertiesMember as PropertyInfo)?.GetValue(value);

switch (additionalProperties)
{
case IDictionary<string, JToken> additionalPropertiesJToken:
HandleAdditionalProperties(additionalPropertiesJToken, name, arraySerializationFormat, keys);
return;

case IDictionary<string, object> additionalPropertiesObj:
HandleAdditionalProperties(additionalPropertiesObj, name, arraySerializationFormat, keys);
return;
}
}

private static void HandleAdditionalProperties<T>(IDictionary<string, T> properties, string name,
ArraySerialization arraySerializationFormat, List<KeyValuePair<string, object>> keys)
{
foreach (var kvp in properties)
{
string fullSubName = string.IsNullOrWhiteSpace(name) ? kvp.Key : $"{name}[{kvp.Key}]";
PrepareFormFieldsFromObject(fullSubName, kvp.Value, arraySerializationFormat, keys, null);
}
}

private static void PrepareFormFieldsForDictionary(string name, IDictionary dictionary, ArraySerialization arraySerializationFormat, List<KeyValuePair<string, object>> keys = null, PropertyInfo propInfo = null)
private static void PrepareFormFieldsForDictionary(string name, IDictionary dictionary,
ArraySerialization arraySerializationFormat, List<KeyValuePair<string, object>> keys = null,
PropertyInfo propInfo = null)
{
foreach (var sName in dictionary.Keys)
{
Expand Down
Loading