2

I'm running into an issue I don't know how to resolve. I need to return a JSON object that has duplicate keys.

I understand this is perfectly valid according to the JSON specification. See ecma-404, page 6 that reads:

The JSON syntax does not impose any restrictions on the strings used as names, does not require that name strings be unique...

I need to return this:

{
  "id":"1401",
  "sku":"C18-25",
  "sku":"A15-70"
}

I've been using a typical rest method in Spring Boot:

@GetMapping("/{id}")
@ResponseBody
public ResponseEntity<?> getProduct(@PathVariable String id) {
  Map<String, String> r = new HashMap<>();
  r.put("id", "1401");
  r.put("sku", "C18-25");
  r.put("sku", "A15-70");
  return new ResponseEntity<Map<String, String>>(r, HttpStatus.OK);
}

But it only keeps the last entry:

{
  "id":"1401",
  "sku":"A15-70"
}

How can I return multiple entries with the same name?

Joe DiNottra
  • 836
  • 8
  • 26
  • 1
    Why would you need to do this? – Alexander Ivanchenko Dec 21 '22 at 14:50
  • 1
    [The specs tell you that you](https://www.rfc-editor.org/rfc/rfc8259#section-4) that names within an object SHOULD be unique. SHOULD is not MUST, but it is _highly_ recommended you follow that advice. It can lead to trouble with many tools. Why not make `sku` an array? – Ivar Dec 21 '22 at 14:52
  • That’s non-sensical—any reasonable parsing would keep only the last value. At **best** you could hope for an object that converts keys with multiple values into a collection, but that’s problematic for the consuming code. – Dave Newton Dec 21 '22 at 14:52
  • @AlexanderIvanchenko Unfortunately, it is valid JSON (see reference to the standard) and it's a requirement I need to deal with. Sigh. – Joe DiNottra Dec 21 '22 at 14:55
  • The topic regarding duplicate keys was already discussed in https://stackoverflow.com/a/23195243/224155 – Arturas Gusevas Dec 21 '22 at 14:57
  • @DaveNewton I understand but names can be duplicated in JSON. See reference to the standard. – Joe DiNottra Dec 21 '22 at 14:57
  • @Ivar Taking aside what is the best option, do you know if Spring Boot support this or not? – Joe DiNottra Dec 21 '22 at 15:00
  • 1
    @JoeDiNottra If you're going down the unrecommended road anyway, you might as well break the unwritten "never manually construct JSON" rule. Just construct the JSON yourself and return it as a String. I doubt any of the built-in serializers allow you to generate a JSON with duplicate keys. But honestly, sometimes you have to sell "no" as a software engineer. If someone asks you to break a diamond with your bare hands, then saying "welp, that's the requirement" isn't really an option either. It's just not feasible. – Ivar Dec 21 '22 at 15:04
  • 1
    @Ivar Thank you. I think I'll push back. The client will fight it since "the standard says it's perfectly possible", but I guess it's much safer to avoid this in the long run. Bitter pill to swallow for the client (and for me as well, in consequence). – Joe DiNottra Dec 21 '22 at 15:06
  • @JoeDiNottra I have to say that there are cases where one party requires some format, irrelevant of specs, an then you often don't have other options than to comply with that requirement if you want to get job done. This case here is far from being worst in that regard. – eis Dec 21 '22 at 15:08
  • @JoeDiNottra I see, may bad, I was convinced that it's requirement (not a strong recommendation). – Alexander Ivanchenko Dec 21 '22 at 15:47
  • @JoeDiNottra I didn't say they couldn't--I said it's non-sensical because duplicate keys would delete data w/o warning, or at best, throw an error/warning. – Dave Newton Dec 21 '22 at 16:27

4 Answers4

1

You can just return a string with response entity.

String response = "{\n"+
"  \"id\":\"1401\",\n"+
"  \"sku\":\"C18-25\",\n"+
"  \"sku\":\"A15-70\"\n"+
"}";
return new ResponseEntity<String>(response, HttpStatus.OK);

It is not pretty, but it is supported by Spring and will get the job done.

eis
  • 51,991
  • 13
  • 150
  • 199
0

A HashMap only allows unique key values, so there is no way for you to use HashMap for this purpose. Also while you are correct, you can use duplicate key values in a JSON body, it is not recommended. Why not make sku an array like this?

"sku":["A15-70","C18-25"]
rhowell
  • 1,165
  • 8
  • 21
  • 1
    It's a requirement I need to deal with. If I cannot use a Map, what can I use? – Joe DiNottra Dec 21 '22 at 14:59
  • I suppose you can try to manually create a JSON string for them and return that? Not ideal, but if you need to do it I guess it works. – rhowell Dec 21 '22 at 15:09
0

While creating map create like this

Map<String, List<String>> map = new HashMap<>();

while putting existing key in Map check using containsKey() method and put duplicate value in List of key

you will get result like this

{ "id":"1401", "sku":["A15-70","C18-25"] }

Pratik
  • 21
  • 5
0

Here's how you can construct a JSON-object from an arbitrary number of Map.Entry instances (but obviously if JSON structure would be more complex it'll be better to avoid generating it manually at all costs since it would be tedious and error-prone):

@SafeVarargs
public static String entriesToJson(Map.Entry<String, String>... entries) {
    
    return Arrays.stream(entries)
        .map(e -> "\t\"%s\" : \"%s\"".formatted(e.getKey(), e.getValue()))
        .collect(Collectors.joining(",\n", "{\n", "\n}"));
}

@GetMapping("/{id}")
@ResponseBody
public ResponseEntity<?> getProduct(@PathVariable String id) {
    // your logic
    return ResponseEntity.ok(
        entriesToJson(
            Map.entry("id", "1401"),
            Map.entry("sku", "C18-25"),
            Map.entry("sku", "A15-70")
        )
    );
}

Usage example:

public static void main(String[] args) {
    System.out.println(entriesToJson(
        Map.entry("id", "1401"),
        Map.entry("sku", "C18-25"),
        Map.entry("sku", "A15-70")
    ));
}

Output:

{
    "id" : "1401",
    "sku" : "C18-25",
    "sku" : "A15-70"
}
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46