简体   繁体   中英

Spring boot how to return json embedded in object structure

In my Spring-boot controller I have an endpoint that returns the following data structure:

public ResponseEntity<Map<String, Map<String, Object>>> getComplexStructure()

The inner Object in this structure is saved as raw JSON String in the database and I don't use the actual value in my application. Currently I have to Deserialize that JSON into an Object so that I can return it from the function, so that Spring can Serialize it into JSON again. That sounds inefficient and I was wondering could I somehow skip that Deserialize Reserialize part and just return the raw JSON for Spring to embed in the serialized datastructure surrounding it.

Now obviously if I just return the String like this:

public ResponseEntity<Map<String, Map<String, String>>> getComplexStructure()

the JSON parser will put the string in quotation marks and escape it and it will just be a string and not a json structure.

Is there some annotation or something I could do to skip the unnecessary Serialization and Deserialization of that embedded JSON?

Reading this it sounds very simple, if there are some good technical terms for this process, please also mention them, it will make googling easier:)

You can use AbstractHttpMessageConverter for custom mapping operations for your problem.

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;

@Component
public class CustomMessageConverter extends AbstractHttpMessageConverter<Map<String, Map<String, Object>>> {
    public CustomMessageConverter() {
        super(MediaType.APPLICATION_JSON);
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return HashMap.class.isAssignableFrom(clazz);
    }

    @Override
    protected Map<String, Map<String, Object>> readInternal(Class<? extends Map<String, Map<String, Object>>> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    protected void writeInternal(Map<String, Map<String, Object>> value, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        PrintStream printStream = new PrintStream(outputMessage.getBody());

        printStream.print("{");

        // track to put comma between items
        boolean printedEntry = false;

        for (Map.Entry<String, Map<String, Object>> entry : value.entrySet()) {
            /* // if you want to disable null print uncomment it
            if (entry.getValue() == null) {
                continue;
            }
            */
            if (printedEntry) {
                printStream.print(",");
            }

            printStream.print("\"" + entry.getKey() + "\":");
            if (entry.getValue() == null) {
                printStream.print("null");
                continue;
            }

            // INNER ENTRY LOOP
            printStream.print("{");
            // track to put comma between items
            boolean printedInnerEntry = false;
            for (Map.Entry<String, Object> innerEntry : entry.getValue().entrySet()) {
                if (printedInnerEntry) {
                    printStream.print(",");
                }
                printStream.print("\"" + innerEntry.getKey() + "\":");
                printStream.print(innerEntry.getValue());
                printedInnerEntry = true;
            }
            printStream.print("}");

            printedEntry = true;
        }
        printStream.print("}");
    }
}

Test

@RestController
public class HomeController {
    @GetMapping
    public ResponseEntity<Map<String, Map<String, Object>>> getComplexStructure() {
        Map<String, Map<String, Object>> result = new HashMap<>();

        Map<String, Object> innerItem1 = new HashMap<>();
        innerItem1.put("bar1", "{\"item\": 12, \"list\": [1, 2]}");
        innerItem1.put("bar2", "{\"item\": 22, \"test\": \"test@\"}");

        result.put("foo1", innerItem1);

        Map<String, Object> innerItem2 = new HashMap<>();
        innerItem2.put("tail1", "{\"item\": 55}");
        innerItem2.put("tail2", "{\"item\": 77}");

        result.put("foo2", innerItem2);

        return ResponseEntity.ok(result);
    }
}

GET http://localhost:8080/

Output

{"foo1":{"bar1":{"item": 12, "list": [1, 2]},"bar2":{"item": 22, "test": "test@"}},"foo2":{"tail1":{"item": 55},"tail2":{"item": 77}}}

Caution

I recommend that create a new class for operating this special case. Because HashMap is generic and caught all unrelated returns extended from HashMap . So, if you want to prefer this way, you can change below codes.

Create a new return type from HashMap<String, Map<String, Object>>

public class CustomMessage extends HashMap<String, Map<String, Object>> {
}

In CustomMessageConverter change supports method with below.

@Override
protected boolean supports(Class<?> clazz) {
   return CustomMessage.class.isAssignableFrom(clazz);
}

Also, you can change AbstractHttpMessageConverter<CustomMessage> instead of AbstractHttpMessageConverter<Map<String, Map<String, Object>>> but it doesn't required.

Change return type with this new class.

public ResponseEntity<CustomMessage> getComplexStructure() {
   CustomMessage result = new CustomMessage();
   ...
   return ResponseEntity.ok(result);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM