Json Representation of Map with Complex Key

I want to serialize to JSON the following (java) data structure:

class Machine {
  String name;
  Map<PartDescriptor, Part> parts;
}

class PartDescriptor {
  String group;
  String id;

  hashCode()
  equals()
}

class Part {
  String group;
  String id;
  String description;
  String compat;
  ...
  ...
}

What would be JSON representation of one Machine?

Also (optional), point me to a JSON to Java serializer/deserializer that will support your representation

2

6 Answers

I'd do something like:

{
  "name": "machine name",
  "parts": [
     { "group": "part group", "id": "part id", "description": "...", ... },
     { "group": "part group", "id": "part id", "description": "...", ... },
     // ...
  ]
}

If the "id" for each Part is unique, then the "parts" property can be an object instead of an array, with the "id" of each part serving as the key.

{
  "name": "machine name",
  "parts": {
     "1st part id": { "group": "part group", "description": "...", ... },
     "2nd part id": { "group": "part group", "description": "...", ... },
     // ...
  }
}
1

You don't need annotations or custom serializers. Assuming you already have getters for all the fields in Part and Machine, all that's really missing is a toString() on PartDescriptor. If, for some reason, you don't have getter functions, you'll need to annotate the fields of interest with @JsonProperty so Jackson knows which fields to include in the serialized output. However, it's preferable (and easier) to simply create getters.

The toString() on PartDescriptor should return the key you want to use in your mapping. As another answer suggests, you might simply concatenate the relevant fields:

@Override
public String toString() {
    return group + "|" + id;
}

Then you'll magically get this form when you attempt to serialize a Machine with Jackson's ObjectMapper:

{
  "name" : "Toaster",
  "parts" : {
    "Electrical|Descriptor1" : {
      "group" : "Electrical",
      "id" : "Part1",
      "description" : "Heating Element",
      "compat" : "B293"
    },
    "Exterior|Descriptor2" : {
      "group" : "Exterior",
      "id" : "Part2",
      "description" : "Lever",
      "compat" : "18A"
    }
  }
}
8

I would do this. The parts key of the top level object would be a JSONArray of JSONObject that have key's and value's. The key would be an object that is your PartDescriptor and the value would be your Part.

{
    "name":"theName",
    "parts":[
        {
            "key":{
                       "group":"theGroup",
                       "id":"theId"
                  },
            "value":{
                       "group":"theGroup",
                       "id":"theId",
                       "description":"theDescription",
                       "compat":"theCompat",
                       ...
                    }
        },
        ...
    ]
}

Assuming that group+id gives a unique combination, and that ":" is a permissible delimiter:

{  
   "name": "machine name",
   "parts": { 
               "somegroup:01465": {
                                    "group":"somegroup",
                                    "id": "01465",
                                    ...
                                  },
               "othergroup:32409": {
                                     "group":"othergroup",
                                     "id": "32409",
                                     ...
                                   }

            }
}

JSON requires the key to be a string, so if you truly need the data to be represented as keyed (e.g. you don't want to use an array, like in Pointy's answer, because you'd like to guarantee it in the contract that there are no duplicate entries with the same key) then you'd need to decide yourself on a way to serialize the complex key into a string.

Two things to note if going with an approach that uses concatenating with a separator (e.g. group1|part1):

  • You'll want a separator that cannot itself occur in the key parts, or you'll need to escape it when serializing (e.g. double it). The problems that this prevents might be rare to run into, but if this is to be written in a reusable, general purpose code, it should better be guaranteed, as per Murphy's law - if something can go wrong, it eventually will.
  • To truly prevent 'more than one value with same compound key', you'll want to maintain the same order of the keys, e.g. sort the key alphabetically

Whereas re:

Also (optional), point me to a JSON to Java serializer/deserializer that will support your representation

One notable example might be Gson - Google's JSON serializer library for Java - which uses this representation:

{
     "(group1,part1)": { description: ... },
     "(group1,part2)": { description: ... },
     "(group2,part1)": { description: ... },
     ...
     "(groupX,partX)": {description: ... },
}

Note: the feature needs to be enabled by setting enableComplexMapKeySerialization (off by default for backwards compatibility)

It can be rendered as the following table:

<table class="machine" name="">
   <tr>
     <th class="partdescriptor" colspan="2">
     <th class="part" colspan="4">
   </tr>
   <tr>
     <td class="partdescriptor group"></td>
     <td class="partdescriptor" id=""></td>
     <td class="part group"></td>
     <td class="part" id=""></td>
     <td class="description"></td>
     <td class="compat"></td>
    </tr>
 </table>

The markup decomposes into the following JSON object due to the lack of metadata via attributes:

{
    "HTMLTableElement": 
    [
        {
            "classname": "machine",
            "name": ""
        },
        {
            "HTMLTableRowElement": 
            [
                {
                    "HTMLTableCellElement": {"classname":"partdescriptor","colspan":2}
                },
                {
                    "HTMLTableCellElement": {"classname":"part","colspan":4}
                }
            ]
        },
        {
            "HTMLTableRowElement": 
            [
                {
                    "HTMLTableCellElement": {"classname":"partdescriptor group"}
                },
                {
                    "HTMLTableCellElement": {"classname":"partdescriptor","id":""}
                },
                {
                    "HTMLTableCellElement": {"classname":"part","id":""}
                },
                {
                    "HTMLTableCellElement": {"classname":"description"}
                },
                {
                    "HTMLTableCellElement": {"classname":"compat"}
                }
            ]
        }
    ]
}

Alternatively, Unicode can simplify the mapping:

{"name":"","[{\u0022group\u0022:\u0022\u0022},{\u0022id\u0022:\u0022\u0022}]":
 [
 {"group":""},
 {"id":""},
 {"description":""},
 {"compat":""}
 ]
}

Which can be stringified:

JSON.stringify({"name":"","[{\u0022group\u0022:\u0022\u0022},{\u0022id\u0022:\u0022\u0022}":[{"group":""},{"id":""},{"description":""},{"compat":""}]})

to produce:

"{\"name\":\"\",\"[{\\\"group\\\":\\\"\\\"},{\\\"id\\\":\\\"\\\"}]\":[{\"group\":\"\"},{\"id\":\"\"},{\"description\":\"\"},{\"compat\":\"\"}]}"

which can be parsed:

JSON.parse("{\"name\":\"\",\"[{\\\"group\\\":\\\"\\\"},{\\\"id\\\":\\\"\\\"}]\":[{\"group\":\"\"},{\"id\":\"\"},{\"description\":\"\"},{\"compat\":\"\"}]}")

to produce an object literal:

({name:"", '[{"group":""},{"id":""}]':[{group:""}, {id:""}, {description:""}, {compat:""}]})

References

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Sophia Al-Mansoor

Sophia Al-Mansoor

Global Business & E-Commerce Reporter

Sophia analyzes international trade, startup ecosystems, retail transformation, and supply chain logistics for modern digital publications.

Share this article
Twitter Facebook Pinterest