52

I need to document with Swagger an API that uses, both as input and output, maps of objects, indexed by string keys.

Example:

{
    "a_property": {
        "foo": {
            "property_1": "a string 1",
            "property_2": "a string 2"
        },
        "bar": {
            "property_1": "a string 3",
            "property_2": "a string 4"
        }
    }
}

"foo" and "bar" can be any string keys, but they should be unique among the set of keys.

I know that, with Swagger, I can define an array of objects, but this gives a different API since we then would have something as:

{
    "a_property": [
        {
            "key": "foo"
            "property_1": "a string 1",
            "property_2": "a string 2"
        },
        {
            "key": "bar"
            "property_1": "a string 3",
            "property_2": "a string 4"
        }
    ]
}

I have read the 'Open API Specification' - 'Add support for Map data types #38' page. As far as I understand, it recommends to use additionalProperties, but it doesn't seem to answer my need (or it doesn't work with Swagger UI 2.1.4 that I use). Did I miss something?

So far I have found the following work-around (in Swagger JSON):

a_property: {
    description: "This is a map that can contain several objects indexed by different keys.",
    type: object,
    properties: {
        key: {
            description: "map item",
            type: "object",
            properties: {
                property_1: {
                    description: "first property",
                    type: string
                },
                property_2: {
                    description: "second property",
                    type: string
                }
            }
        }
    }
}

This almost does the job, but the reader has to understand that "key" can be any string, and can be repeated several times.

Is there a better way to achieve what I need?

Helen
  • 87,344
  • 17
  • 243
  • 314
Xavier Lamorlette
  • 1,152
  • 1
  • 12
  • 20
  • 2
    Personally it took me some time to understand *why* `additionalProperties` is the right answer: http://stackoverflow.com/a/41240118/110488 – Chen Levy Dec 20 '16 at 10:47

4 Answers4

60

Using additionalPropertiesis the proper way to describe hashmap with OpenAPI (fka. Swagger) Specification but Swagger UI do not render them for now.

The issue is tracked here https://github.com/swagger-api/swagger-ui/issues/1248

Meanwhile you can use this trick: define a non required property (defaultin the example below) of the same type of the map's objects and give some hint within the description:

swagger: "2.0"
info:
  version: 1.0.0
  title: Hashmap
  
paths: {}

definitions:
  MapItem:
    properties:
      firstname:
        type: string
      lastname:
        type: string
  Map:
    description: a (key, MapItem) map. `default`is an example key
    properties:
      default:
        $ref: '#/definitions/MapItem'
    additionalProperties:
      $ref: '#/definitions/MapItem'

This description does not modify API contract and improves documentation.

Saurabh Garg
  • 39
  • 11
Arnaud Lauret
  • 4,961
  • 1
  • 22
  • 28
  • Thanks for finding the reference of the related issue in SwaggerUI. Unfortunately I don't have yet enough reputation to vote up your answer ;-) – Xavier Lamorlette May 17 '16 at 07:11
  • 1
    As of this date, at least the javascript version of swagger-codegen ignores additional properties, so it could be a showstopper for some – 1800 INFORMATION Jun 09 '17 at 06:05
  • additional properties just won't do it. Additional properties fail to address the fact that my value can have a specific schema – Ajay May 03 '19 at 15:28
  • Great answer, but still not clear how to define `string` as a key type (in case of `$ref` as a key). It is not clear from https://swagger.io/docs/specification/data-models/dictionaries/ as well. – Dmitriy Popov Dec 29 '20 at 06:34
7

By using additionalProperties:

definitions:
  String-StringStringMap: # <-- use this as your result
    type: object
    additionalProperties:
      $ref: "#/definitions/StringStringMap"

  StringStringMap:
      type: object
      additionalProperties:
        type: string

this results a 2 level map:

{
  "additionalProp1": {
    "additionalProp1": "string",
    "additionalProp2": "string",
    "additionalProp3": "string"
  },
  "additionalProp2": {
    "additionalProp1": "string",
    "additionalProp2": "string",
    "additionalProp3": "string"
  },
  "additionalProp3": {
    "additionalProp1": "string",
    "additionalProp2": "string",
    "additionalProp3": "string"
  }
}

With same idea you can specify a 3 level map also.

Daniel Hári
  • 7,254
  • 5
  • 39
  • 54
5

If I understand it correctly, the basic problem is that there is no universally accepted JSON mapping for a Java Map, especially when the key is more complex than a string. I have seen that GSON takes one approach (treat the key as an object), whereas Jackson takes another (serialise the key to a string). The c# equivalent to a Map (a Dictionary) uses a third approach (treating each entry as a key-value object in its own right with properties called "Key" and "Value"). As Swagger tries to be agnostic to language and serialiser, this puts it in an impossible position.

John Denniston
  • 167
  • 1
  • 6
1

you can simply use type as object. When we are parsing data from frontend we have no such thing Map<Key,value>. we are just sending objects. Map is up to backend stuf. that is why I am asking to use object as the type. In objects we can send key value pairs. as the example given below

  metaData:
    type: object
    example: {
      "heading":"comfirmation email"
    }
  • Can you clarify what you mean? As is, it's not entirely clear. That's especially important here, since there's an answer that's been widely validated by the community with 50 upvotes. – Jeremy Caney Nov 30 '21 at 00:38
  • @JeremyCaney A map means that is a set of key value pairs. We are not passing Map kind a thing from frontend. Instead of that we are parsing a object from frontend. that is why I asked to use object as the data type in that. this is an example I have used `metaData: type: object example: { "heading":"comfirmation email" }` – Isuru Maldeniya Nov 30 '21 at 04:36
  • That’s helpful! Can I suggest [edit]ing your answer to include that detail? – Jeremy Caney Nov 30 '21 at 04:42
  • Please add your comment suggestion to improve the answer . – George Nov 30 '21 at 05:49
  • @JeremyCaney yes you can edit – Isuru Maldeniya Nov 30 '21 at 06:29
  • @IsuruMaldeniya: Can you edit? It's your answer. – Jeremy Caney Nov 30 '21 at 06:32