Loop to generate JSON

Viewed 117

I'm implementing Java SpringBoot to make RestAPI application. I stuck with the loop below. The problem seems with the sub1_arr.add(subResult1);

I have run few test:

  1. Print the SubResult1 for each iteration: The println show correct element but when add to sub1_arr it's become duplicate.

  2. Test 1 (as shows in code) : This is suppose the initial code I wrote. Output is incorrect with duplicate output

  3. Test 2 (as show in code) : having correct output but in string form.

    while(rs.next())
    {
        if(rs.getString("status").equals("FAILED")) {
            obj.put("result", "failed"); 
            obj.put("msg", rs.getString("msg"));
        } 
        else {

            //mainResult - ignore for a while
            mainResult.put("layer1_id", rs.getString("layer1_id"));
            mainResult.put("layer1_name", rs.getString("layer1_name"));

            //subResult - layer2
            subResult1.put("layer2_id", rs.getString("layer2_id"));
            subResult1.put("layer2_name", rs.getString("layer2_name"));

            //test 1 - having incorrect output with all the element become duplicate
            sub1_arr.add(subResult1);

            //test 2 - having correct output but the output become string with \"xxx\"
            sub1_arr.add(subResult1.toString());
        }

    }

    //adding sub array to main result
    mainResult.set("layer2", sub1_arr);

Get output (unexpected)

{"layer2":[
   {"layer2_id" : "3", "layer2_name" : "name_3"},
   {"layer2_id" : "3", "layer2_name" : "name_3"},
   {"layer2_id" : "3", "layer2_name" : "name_3"} 
]}

Expected output

{"layer2":[
   {"layer2_id" : "1", "layer2_name" : "name_1"},
   {"layer2_id" : "2", "layer2_name" : "name_2"},
   {"layer2_id" : "3", "layer2_name" : "name_3"} 
]}
2 Answers

This is because subResult1 object is of type Map, and the values is being overridden in each loop, as the key - being same - is being overridden. As all the elements of sub1_arr is pointing to that same object, all of them will show you the same result.

Instead, you might want to try something like that(a better way to get a json output - don't re-invent the wheel):

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.createObjectNode();
ArrayNode arrayNode = mapper.createArrayNode();

while(rs.next()) {
    ObjectNode layer = mapper.createObjectNode();   
    layer.put("layer2_id", rs.getString("layer2_id"));
    layer.put("layer2_name", rs.getString("layer2_name"));
    arrayNode.add(layer);
}

((ObjectNode) rootNode).set("layer2", arrayNode);
String jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
System.out.println(jsonString);

This is my test:

package com.example;

import java.util.Arrays;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class Main {

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode rootNode = mapper.createObjectNode();

        ObjectNode layer1 = mapper.createObjectNode();
        layer1.put("layer2_id", "1");
        layer1.put("layer2_name", "name_1");

        ObjectNode layer2 = mapper.createObjectNode();
        layer2.put("layer2_id2", "2");
        layer2.put("layer2_name2", "name_2");

        ObjectNode layer3 = mapper.createObjectNode();
        layer3.put("layer2_id2", "3");
        layer3.put("layer2_name2", "name_3");

        ArrayNode arrayNode = mapper.createArrayNode();
        arrayNode.addAll(Arrays.asList(layer1, layer2, layer3));

        ((ObjectNode) rootNode).set("layer2", arrayNode);

        String jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
        System.out.println(jsonString);
    }
}

Output:
{"layer2":[
{"layer2_id" : "1", "layer2_name" : "name_1"},
{"layer2_id" : "2", "layer2_name" : "name_2"},
{"layer2_id" : "3", "layer2_name" : "name_3"}
]}

This is a common problem for new developers, when I was a programming tutor this was one of the most common ones after people figured out how to use loops and arrays. The problem lies in the fact that subresult1 is actually a reference to an object. When you use this line sub1_arr.add(subResult1) it's always the same reference in memory, so by the time you get to the last iteration, all of the values are the same because you've been updating the same object in memory. The way you would solve this is by adding a new to the subResult1 object. I don't know what type of object you have because it's not included in the sample, but let's say that above you have this:

Map<String, String> subResult1 = new HashMap<String, String>();

In your code you would need to add this:

//subResult - layer2 subResult1 = new HashMap();

What this does is essentially overwrite the memory reference to your original Map and creates a new one with a reference to the new Map. Now, the old map is still there because you've added it to sub1_arr. But when you assign the values using the 'put' it will be assigning them to your new object.

There are a lot of ways you can improve this, but to start with, generally I like to declare variables as close to the point I need them as I can (without creating too much clutter). So I would move your declaration of subresult1 to the same place and just eliminate it above the loop. Like so:

//subResult - layer2
Map<String, String> subResult1 = new HashMap<String, String>();

Hope this helps!

Related