ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

REST Client Layer: JSON Payload Transformation

Groovy

Goal -- WPM

Ready
Exercise Algorithm Area
1import groovy.json.JsonSlurper
2import groovy.json.JsonOutput
3import java.util.Map
4import java.util.List
5import java.util.HashMap
6import java.util.ArrayList
7
8class JsonTransformer {
9
10/**
11* Transforms a source JSON object into a target JSON object based on a mapping configuration.
12* Handles nested objects, arrays, conditional mapping, and type conversions.
13*
14* @param sourceJson The source JSON object (as a Map).
15* @param mappingConfig The configuration defining how to transform the source to the target.
16* Format: Map<String, Object> where values can be:
17* - String: Target field name, value is copied directly.
18* - Map: Nested mapping configuration for nested objects.
19* - List: Defines transformation for array elements.
20* - Closure: Custom transformation logic.
21* @return The transformed JSON object (as a Map).
22*/
23public Map<String, Object> transform(Map<String, Object> sourceJson, Map<String, Object> mappingConfig) {
24if (sourceJson == null || mappingConfig == null) {
25throw new IllegalArgumentException("Source JSON and mapping configuration cannot be null.")
26}
27return transformRecursive(sourceJson, mappingConfig)
28}
29
30private Map<String, Object> transformRecursive(Map<String, Object> source, Map<String, Object> config) {
31Map<String, Object> target = new HashMap<>()
32
33for (Map.Entry<String, Object> entry : config.entrySet()) {
34String targetKey = entry.getKey()
35Object configValue = entry.getValue()
36
37if (configValue instanceof String) { // Direct mapping or renaming
38String sourceKey = (String) configValue
39if (source.containsKey(sourceKey)) {
40target.put(targetKey, source.get(sourceKey))
41} else {
42// Handle missing source field - could log a warning or throw error
43println "Warning: Source field '${sourceKey}' not found for target key '${targetKey}'."
44}
45} else if (configValue instanceof Map) { // Nested object mapping
46Map<String, Object> nestedSource = (Map<String, Object>) source.get(targetKey) // Assume source key matches target key for nested
47if (nestedSource == null) {
48// If nested source is missing, create an empty target nested object or skip
49println "Warning: Nested source object for key '${targetKey}' not found. Creating empty target object."
50target.put(targetKey, new HashMap<String, Object>()) // Or skip if preferred
51} else {
52target.put(targetKey, transformRecursive(nestedSource, (Map<String, Object>) configValue))
53}
54} else if (configValue instanceof List) { // Array mapping
55// Assumes the list in config defines how to transform each element of a source array.
56// The source key is assumed to be the targetKey.
57Object sourceArrayObj = source.get(targetKey)
58if (sourceArrayObj instanceof List) {
59List<Object> sourceArray = (List<Object>) sourceArrayObj
60List<Object> transformedArray = new ArrayList<>()
61Map<String, Object> elementConfig = configValue.get(0) instanceof Map ? (Map<String, Object>) configValue.get(0) : null // Assuming first element defines element transformation
62
63if (elementConfig != null) {
64for (Object item : sourceArray) {
65if (item instanceof Map) {
66transformedArray.add(transformRecursive((Map<String, Object>) item, elementConfig))
67} else {
68// Handle non-map items in source array if needed
69transformedArray.add(item) // Copy as-is or handle error
70}
71}
72target.put(targetKey, transformedArray)
73} else {
74// If config list doesn't define element transformation, copy array as-is or error
75println "Warning: Array transformation config missing for key '${targetKey}'. Copying array as-is."
76target.put(targetKey, sourceArray)
77}
78} else {
79// Source is not an array where an array was expected
80println "Warning: Expected an array for key '${targetKey}' in source, but found: ${sourceArrayObj.getClass().getName()}."
81}
82} else if (configValue instanceof Closure) { // Custom transformation using Closure
83Closure customTransform = (Closure) configValue
84// The closure receives the source JSON and the target JSON being built
85// It can modify the target directly or return a value to be put into the target
86Object result = customTransform.call(source, target)
87if (result != null) {
88// If closure returns a value, it might be for a specific field or the whole target
89// This simple example assumes it might return a value for the current targetKey
90// A more complex system would define how closures interact with target keys.
91// For now, we'll assume the closure modifies 'target' directly or returns a value for 'targetKey'
92// If the closure returns a Map, it could be assigned to targetKey
93if (result instanceof Map) {
94target.put(targetKey, result)
95} else {
96// If closure returns a primitive or other object, and targetKey is specified
97// This part is tricky and depends on closure design.
98// For simplicity, if closure returns a non-Map, we might ignore it or assign it if targetKey is explicit.
99// Let's assume for now the closure modifies 'target' directly.
100}
101}
102} else {
103// Handle other types or unsupported configurations
104println "Warning: Unsupported configuration type for key '${targetKey}': ${configValue.getClass().getName()}"
105}
106}
107return target
108}
109
110public static void main(String[] args) {
111JsonTransformer transformer = new JsonTransformer()
112
113// --- Example 1: Basic field mapping and renaming ---
114Map<String, Object> source1 = [
115'userId': 123,
116'userName': 'Alice',
117'emailAddress': 'alice@example.com'
118]
119Map<String, Object> config1 = [
120'id': 'userId',
121'name': 'userName',
122'contactEmail': 'emailAddress'
123]
124Map<String, Object> transformed1 = transformer.transform(source1, config1)
125println "Transformed 1: ${JsonOutput.toJson(transformed1)}"
126// Expected: {"id":123,"name":"Alice","contactEmail":"alice@example.com"}
127
128// --- Example 2: Nested objects and array transformation ---
129Map<String, Object> source2 = [
130'orderId': 'ORD789',
131'customerInfo': [
132'firstName': 'Bob',
133'lastName': 'Smith',
134'address': [
135'street': '123 Main St',
136'city': 'Anytown'
137]
138],
139'items': [
140[
141'productId': 'P101',
142'productName': 'Gadget',
143'quantity': 2,
144'price': 19.99
145],
146[
147'productId': 'P102',
148'productName': 'Widget',
149'quantity': 1,
150'price': 29.99
151]
152]
153]
154Map<String, Object> config2 = [
155'orderReference': 'orderId',
156'customer': [
157'firstName': 'firstName',
158'lastName': 'lastName',
159'location': [
160'street': 'street',
161'city': 'city'
162]
163],
164'products': [ [
165'sku': 'productId',
166'name': 'productName',
167'qty': 'quantity',
168'unitPrice': 'price'
169] ]
170]
171Map<String, Object> transformed2 = transformer.transform(source2, config2)
172println "Transformed 2: ${JsonOutput.toJson(transformed2)}"
173// Expected: {"orderReference":"ORD789","customer":{"firstName":"Bob","lastName":"Smith","location":{"street":"123 Main St","city":"Anytown"}},"products":[{"sku":"P101","name":"Gadget","qty":2,"unitPrice":19.99},{"sku":"P102","name":"Widget","qty":1,"unitPrice":29.99}]}
174
175// --- Example 3: Conditional mapping using Closure ---
176Map<String, Object> source3 = [
177'status': 'ACTIVE',
178'isActive': true,
179'value': 100,
180'secondaryValue': 50
181]
182Map<String, Object> config3 = [
183'currentStatus': 'status',
184'displayValue': { source, target ->
185// Custom logic: if status is ACTIVE, use 'value', otherwise use 'secondaryValue'
186if (source.get('status') == 'ACTIVE' && source.get('isActive')) {
187target.put('displayValue', source.get('value'))
188} else {
189target.put('displayValue', source.get('secondaryValue'))
190}
191}
192]
193Map<String, Object> transformed3 = transformer.transform(source3, config3)
194println "Transformed 3: ${JsonOutput.toJson(transformed3)}"
195// Expected: {"currentStatus":"ACTIVE","displayValue":100}
196
197// --- Example 4: Handling missing source fields and arrays ---
198Map<String, Object> source4 = [
199'id': 'XYZ',
200'details': [
201'tags': ['A', 'B'] // Missing 'name' for tags
202]
203]
204Map<String, Object> config4 = [
205'reference': 'id',
206'nested': [
207'tags': [ [
208'name': 'tag',
209'value': 'value'
210] ]
211]
212]
213Map<String, Object> transformed4 = transformer.transform(source4, config4)
214println "Transformed 4: ${JsonOutput.toJson(transformed4)}"
215// Expected: {"reference":"XYZ","nested":{"tags":[{},{}]}} (with warnings about missing fields)
216
217// --- Example 5: Empty source and config ---
218try {
219transformer.transform(null, config1)
220} catch (e) {
221println "Exception 5a: ${e.getMessage()}" // Expected: Source JSON and mapping configuration cannot be null.
222}
223try {
224transformer.transform(source1, null)
225} catch (e) {
226println "Exception 5b: ${e.getMessage()}" // Expected: Source JSON and mapping configuration cannot be null.
227}
228Map<String, Object> emptySource = [:]
229Map<String, Object> emptyConfig = [:]
230Map<String, Object> transformed5 = transformer.transform(emptySource, emptyConfig)
231println "Transformed 5 (empty): ${JsonOutput.toJson(transformed5)}" // Expected: {}
232}
233}
Algorithm description viewbox

REST Client Layer: JSON Payload Transformation

Algorithm description:

This Groovy code provides a flexible JSON transformation utility for REST client layers. It allows mapping fields from a source JSON to a target JSON based on a configuration map. The transformer supports direct field renaming, nested object mapping, array element transformation, and custom transformations using Groovy closures. It also includes basic error handling for missing fields and invalid inputs, making it robust for preparing data for API requests.

Algorithm explanation:

The `JsonTransformer` class uses a recursive approach to map JSON structures. The `transform` method initializes the process, and `transformRecursive` handles the core logic. It iterates through the `mappingConfig`. If a value is a string, it's treated as a source key to copy. If it's a map, it recursively calls `transformRecursive` for nested objects. If it's a list, it assumes the first element defines the transformation for array items and iterates through the source array. If it's a closure, it executes the custom logic. The algorithm handles missing source fields by issuing warnings. The time complexity is roughly O(N * M), where N is the number of elements in the source JSON and M is the complexity of the mapping configuration, as each relevant field and structure is visited. Space complexity is O(D), where D is the depth of the nested JSON structures and mapping configuration, due to recursion and the creation of the new target map. Edge cases include null inputs, missing source fields, non-array types where arrays are expected, and unsupported configuration types.

Pseudocode:

function transform(sourceJson, mappingConfig):
  if sourceJson is null or mappingConfig is null:
    throw IllegalArgumentException
  return transformRecursive(sourceJson, mappingConfig)

function transformRecursive(source, config):
  target = new empty map
  for each targetKey, configValue in config:
    if configValue is a string (sourceKey):
      if source contains sourceKey:
        target[targetKey] = source[sourceKey]
      else:
        print warning 'Missing source field'
    else if configValue is a map (nestedConfig):
      nestedSource = source[targetKey] // Assume source key matches target key
      if nestedSource is null or not a map:
        print warning 'Missing or invalid nested source'
        target[targetKey] = new empty map // or skip
      else:
        target[targetKey] = transformRecursive(nestedSource, nestedConfig)
    else if configValue is a list (arrayConfig):
      sourceArrayObj = source[targetKey]
      if sourceArrayObj is a list:
        sourceArray = sourceArrayObj
        transformedArray = new empty list
        elementConfig = arrayConfig[0] // Assuming first element defines element transformation
        if elementConfig is a map:
          for each item in sourceArray:
            if item is a map:
              transformedArray.add(transformRecursive(item, elementConfig))
            else:
              transformedArray.add(item) // copy as-is
          target[targetKey] = transformedArray
        else:
          print warning 'Array config missing'
          target[targetKey] = sourceArray // copy as-is
      else:
        print warning 'Expected array, found different type'
    else if configValue is a closure:
      // Execute closure, assuming it modifies 'target' or returns a value for targetKey
      result = closure.call(source, target)
      if result is a map and targetKey is defined:
        target[targetKey] = result
    else:
      print warning 'Unsupported config type'
  return target