Skip to content

Commit bbfcb62

Browse files
sufyankhanraohaseeb-mhrhamzamahmood
authored
feat(additional-properties): add typed additional properties support for models (#106)
## What This PR adds the handling for the typed additional properties in serialization (form and JSON) and de-serialization. Also, contains the tests for the newly added changes. ## Why To add support for the typed additional properties in the SDK Closes #105 --------- Co-authored-by: Muhammad Haseeb <muhammad.haseeb@apimatic.io> Co-authored-by: Hamza Mahmood <hamzamehmood18@gmail.com>
1 parent 04b12cb commit bbfcb62

23 files changed

Lines changed: 3718 additions & 121 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ Core lib's Maven group ID is `io.apimatic`, and its artifact ID is `core`.
6464
| [`CoreJsonObject`](./src/main/java/io/apimatic/core/utilities/CoreJsonObject.java) | Wrapper class for JSON object |
6565
| [`CoreJsonValue`](./src/main/java/io/apimatic/core/utilities/CoreJsonValue.java) | Wrapper class for JSON value |
6666
| [`TestHelper`](./src/main/java/io/apimatic/core/utilities/TestHelper.java) | Contains utility methods for comparing objects, arrays and files |
67+
| [`AdditionalProperties`](./src/main/java/io/apimatic/core/types/AdditionalProperties.java) | A generic class for managing additional properties in a model. |
68+
| [`ConversionHelper`](./src/main/java/io/apimatic/core/utilities/ConversionHelper.java) | A Helper class for the coversion of type (provided as function) for all structures (array, map, array of map, n-dimensional arrays etc) supported in the SDK. |
6769

6870
## Interfaces
6971

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package io.apimatic.core.types;
2+
3+
import java.lang.reflect.Method;
4+
import java.util.HashSet;
5+
import java.util.LinkedHashMap;
6+
import java.util.Map;
7+
import java.util.Set;
8+
import java.util.stream.Collectors;
9+
10+
import com.fasterxml.jackson.annotation.JsonGetter;
11+
12+
/**
13+
* A generic class for managing additional properties in a model.
14+
*
15+
* @param <T> the type of the additional properties.
16+
*/
17+
public class AdditionalProperties<T> {
18+
19+
/**
20+
* Map to store additional properties.
21+
*/
22+
private final Map<String, T> properties = new LinkedHashMap<>();
23+
24+
/**
25+
* Set to store model properties.
26+
*/
27+
private final Set<String> modelProperties = new HashSet<>();
28+
29+
/**
30+
* Default constructor.
31+
*/
32+
public AdditionalProperties() {
33+
// Default constructor
34+
}
35+
36+
/**
37+
* Parameterized constructor.
38+
* @param classInstance The instance of the class with additional properties.
39+
*/
40+
public AdditionalProperties(final Class<?> classInstance) {
41+
Method[] methods = classInstance.getMethods();
42+
for (Method method : methods) {
43+
JsonGetter jsonGetter = method.getAnnotation(JsonGetter.class);
44+
if (jsonGetter != null) {
45+
modelProperties.add(jsonGetter.value());
46+
}
47+
}
48+
}
49+
50+
/**
51+
* Gets the additional properties.
52+
* @return the map of additional properties.
53+
*/
54+
public Map<String, T> getAdditionalProperties() {
55+
return properties;
56+
}
57+
58+
/**
59+
* Sets an additional property.
60+
* @param key The key for the additional property.
61+
* @param value The value of the additional property.
62+
* @throws IllegalArgumentException if there is a conflict between the key and
63+
* any model property.
64+
*/
65+
public void setAdditionalProperty(String key, T value) {
66+
if (key == null || key.trim().isEmpty()) {
67+
throw new IllegalArgumentException("Key cannot be null or empty.");
68+
}
69+
70+
if (modelProperties.contains(key)) {
71+
throw new IllegalArgumentException(
72+
"Key '" + key + "' conflicts with a model property.");
73+
}
74+
75+
properties.put(key, value);
76+
}
77+
78+
/**
79+
* Sets an additional property with an option to skip null values.
80+
* @param key The key for the additional property.
81+
* @param value The value of the additional property.
82+
* @param skipNullValue If true, null values will be skipped.
83+
* @throws IllegalArgumentException if there is a conflict between the key and
84+
* any model property.
85+
*/
86+
public void setAdditionalProperty(String key, T value, boolean skipNullValue) {
87+
if (skipNullValue && value == null) {
88+
return;
89+
}
90+
91+
setAdditionalProperty(key, value);
92+
}
93+
94+
@Override
95+
public String toString() {
96+
if (properties.isEmpty()) {
97+
return "";
98+
}
99+
100+
return properties.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue())
101+
.collect(Collectors.joining(", ", ", ", ""));
102+
}
103+
104+
/**
105+
* Gets an additional property by key.
106+
* @param key The key of the additional property to retrieve.
107+
* @return the value of the additional property associated with the given key,
108+
* or null if not found.
109+
*/
110+
public T getAdditionalProperty(String key) {
111+
return properties.get(key);
112+
}
113+
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package io.apimatic.core.utilities;
2+
3+
import java.util.AbstractMap;
4+
import java.util.List;
5+
import java.util.Map;
6+
import java.util.Objects;
7+
import java.util.function.Function;
8+
import java.util.stream.Collectors;
9+
10+
/**
11+
* A helper class for converting types of various structures supported in the
12+
* SDK.
13+
*/
14+
public final class ConversionHelper {
15+
16+
/**
17+
* Private constructor to prevent instantiation of this utility class.
18+
*/
19+
private ConversionHelper() {
20+
// Prevent instantiation
21+
}
22+
23+
/**
24+
* Converts a single object to the specified type.
25+
*
26+
* @param <S> the type to convert to.
27+
* @param value the object to convert.
28+
* @param conversionFunction the function to apply for conversion.
29+
* @return the converted object of type {@code S}, or null if conversion fails.
30+
*/
31+
public static <S> S convertToSimpleType(Object value, Function<Object, S> conversionFunction) {
32+
try {
33+
return conversionFunction.apply(value);
34+
} catch (Exception e) {
35+
return null;
36+
}
37+
}
38+
39+
/**
40+
* Converts a map of objects to a map of the specified type.
41+
*
42+
* @param <S> the type of values in the resulting map.
43+
* @param value the map of objects to convert.
44+
* @param conversionFunction the function to apply for conversion of each value.
45+
* @return a map with values converted to type {@code S}, or null if conversion
46+
* fails.
47+
*/
48+
@SuppressWarnings("unchecked")
49+
public static <S> Map<String, S> convertToMap(Object value,
50+
Function<Object, S> conversionFunction) {
51+
if (value == null) {
52+
return null;
53+
}
54+
55+
try {
56+
Map<String, Object> valueMap = (Map<String, Object>) value;
57+
return valueMap.entrySet().stream()
58+
.map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(),
59+
convertToSimpleType(entry.getValue(), conversionFunction)))
60+
.filter(entry -> entry.getValue() != null)
61+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
62+
} catch (Exception e) {
63+
return null;
64+
}
65+
}
66+
67+
/**
68+
* Converts a list of objects to a list of the specified type.
69+
*
70+
* @param <S> the type of elements in the resulting list.
71+
* @param value the list of objects to convert.
72+
* @param conversionFunction the function to apply for conversion of each item.
73+
* @return a list with elements converted to type {@code S}, or null if
74+
* conversion fails.
75+
*/
76+
@SuppressWarnings("unchecked")
77+
public static <S> List<S> convertToArray(Object value,
78+
Function<Object, S> conversionFunction) {
79+
if (value == null) {
80+
return null;
81+
}
82+
83+
try {
84+
List<Object> valueList = (List<Object>) value;
85+
return valueList.stream().map(item -> convertToSimpleType(item, conversionFunction))
86+
.filter(Objects::nonNull).collect(Collectors.toList());
87+
} catch (Exception e) {
88+
return null;
89+
}
90+
}
91+
92+
/**
93+
* Converts a list of maps to a list of maps with values of the specified type.
94+
*
95+
* @param <S> the type of values in the maps of the resulting
96+
* list.
97+
* @param value the list of maps to convert.
98+
* @param conversionFunction the function to apply for conversion of each map's
99+
* values.
100+
* @return a list of maps with converted values of type {@code S}, or null if
101+
* conversion fails.
102+
*/
103+
@SuppressWarnings("unchecked")
104+
public static <S> List<Map<String, S>> convertToArrayOfMap(Object value,
105+
Function<Object, S> conversionFunction) {
106+
if (value == null) {
107+
return null;
108+
}
109+
110+
try {
111+
List<Object> valueList = (List<Object>) value;
112+
return valueList.stream().map(item -> convertToMap(item, conversionFunction))
113+
.filter(map -> map != null && !map.isEmpty()).collect(Collectors.toList());
114+
} catch (Exception e) {
115+
return null;
116+
}
117+
}
118+
119+
/**
120+
* Converts a map of lists to a map with lists of the specified type.
121+
*
122+
* @param <S> the type of elements in the lists of the resulting
123+
* map.
124+
* @param value the map of lists to convert.
125+
* @param conversionFunction the function to apply for conversion of each list's
126+
* elements.
127+
* @return a map with lists converted to type {@code S}, or null if conversion
128+
* fails.
129+
*/
130+
@SuppressWarnings("unchecked")
131+
public static <S> Map<String, List<S>> convertToMapOfArray(Object value,
132+
Function<Object, S> conversionFunction) {
133+
if (value == null) {
134+
return null;
135+
}
136+
137+
try {
138+
Map<String, Object> valueMap = (Map<String, Object>) value;
139+
return valueMap.entrySet().stream()
140+
.map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(),
141+
convertToArray(entry.getValue(), conversionFunction)))
142+
.filter(entry -> entry.getValue() != null && !entry.getValue().isEmpty())
143+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
144+
} catch (Exception e) {
145+
return null;
146+
}
147+
}
148+
149+
/**
150+
* Converts an n-dimensional array to a nested list with elements of the
151+
* specified type.
152+
*
153+
* @param <T> the type of the nested structure.
154+
* @param <S> the type of elements in the nested structure.
155+
* @param value the n-dimensional array to convert.
156+
* @param conversionFunction the function to apply for conversion of each
157+
* element.
158+
* @param dimensionCount the depth of the nested structure.
159+
* @return a nested list with elements converted to type {@code S}, or null if
160+
* conversion fails.
161+
*/
162+
@SuppressWarnings("unchecked")
163+
public static <T, S> T convertToNDimensionalArray(Object value,
164+
Function<Object, S> conversionFunction,
165+
int dimensionCount) {
166+
if (value == null) {
167+
return null;
168+
}
169+
170+
try {
171+
return (T) convertToNDimensionalArrayInternal(value,
172+
conversionFunction, dimensionCount);
173+
} catch (Exception e) {
174+
return null;
175+
}
176+
}
177+
178+
/**
179+
* Applies the conversion function to the n-dimensional arrays recursively.
180+
*
181+
* @param <S> the type of elements in the nested structure.
182+
* @param value the n-dimensional array to convert.
183+
* @param conversionFunction the function to apply for conversion of each
184+
* element.
185+
* @param dimensionCount the depth of the nested structure.
186+
* @return a nested list with elements converted to type {@code S}, or null if
187+
* conversion fails.
188+
*/
189+
@SuppressWarnings("unchecked")
190+
private static <S> List<?> convertToNDimensionalArrayInternal(Object value,
191+
Function<Object, S> conversionFunction,
192+
int dimensionCount) {
193+
if (value == null) {
194+
return null;
195+
}
196+
197+
try {
198+
if (dimensionCount == 1) {
199+
return convertToArray(value, conversionFunction);
200+
} else if (dimensionCount > 1) {
201+
List<Object> valueList = (List<Object>) value;
202+
return valueList.stream()
203+
.map(item -> convertToNDimensionalArray(item,
204+
conversionFunction, dimensionCount - 1))
205+
.filter(item -> item != null && !((List<?>) item).isEmpty())
206+
.collect(Collectors.toList());
207+
}
208+
} catch (Exception e) {
209+
// Ignoring exception to handle silently.
210+
}
211+
return null;
212+
}
213+
}

0 commit comments

Comments
 (0)