Managing relational data

Hello,

I am working with two resources: Buildings and Characteristics. The Characteristics stores all the definitions that a building could have. When an characteristic is applied to a building, it receives a value. For example, the “number_desks” characteristic might have a value of 5.

My question is: Can I add this relational data to the relationship resource, and is it possible to retrieve this data through a query?

// /building/4
{
  "data": {
    "type": "building",
    "id": "4",
    "attributes": {
      "name": "Empire State Building"
    },
    "relationships": {
      "characteristics": {
        "links": {
          "self": "buildings/1/relationships/characteristics",
          "related": "buildings/1/characteristics"
        },
        "data": [
          {
            "type": "characteristic",
            "id": "9", // characteristic-id for number_desks
            "attributes": {
              "value": "5"
            }
          },
          { ...}
        ]
      }
    }
  },
  "links": {
    "self": "buildings/4"
  }
}

Is this approach advisable, or do you recommend an alternative solution?

thanks for your feedback!

Greetings!

Presumably you’ve gone this way because characteristics is an extensible enumeration that you want to managed via REST (POST a new characteristic)?
Are all characteristics simply counts? or do some have other attributes like colour, weight, value and so on? If it is variable, then maybe you don’t want such a generic model, but instead need to model each characteristic as a different resource type.
To your specific question, assuming characteristics is extensible and simply countable, I think you have a 3rd resource which is a “characteristic count” - which has an attribute value of “5”, a relationship to characteristic “9” and a relationship to building “4”. This link table approach means each building can have many different countable characteristics

Attributes on relationships are not supported by the JSON:API specification. Using an intermediate resource is recommended instead.

This design decision simplifies the specification and implementations. Allowing attributes in resource identifier objects used in resource linkage seems simple. But it quickly get complex if you take updating those attributes into account.

Thank you for your feedback; it is greatly appreciated.

@PeaDubYa
Indeed, my building will possess several characteristics with simple values assigned to attributes and remarks. I initially thought of aggregating them within an array and managing them through their respective IDs.

@jelhan
My approach was to enable the creation and deletion of relationships, identified by their unique IDs, along with their associated fields.

If we consider using a linked object, the JSON:API implementation might appear as follows:

{
  "data": {
    "type": "buildingCharacteristic",
    "id": "123",
    "attributes": {
      "value": "5",
      "remark": "new desks will be added in the future"
    },
    "relationships": {
      "building": {
        "links": {
          // building
          "self": "/buildingCharacteristic/123/relationships/building",
          "related": "/buildingCharacteristic/123/building"
        }
      },
      "attribute": {
        "links": {
           // characteristic for number_desks
          "self": "/buildingCharacteristic/123/relationships/characteristic",
          "related": "/buildingCharacteristic/123/characteristic"
        }
      }
    }
  },
  "links": {
    "self": "buildingCharacteristic/123"
  }
}

How should we handle the endpoints in this scenario?
When we create the linked object, the relationship with the building will automatically be established. Consequently, attempting to add the relationship through the building endpoint would be invalid. Furthermore, deleting the relationship via the building endpoint would result in the deletion of the linked object, just as if the linked object were deleted directly.

I would recommend only allowing modifications of the relationship by creating and deleting the intermediate resource (buildingCharacteristic in this case). You don’t get any benefit if supporting updating the relationships from building and characteristc to that intermediate resource. Just more complexity and side-effects to manage (and inform the client about).

Thank you, Jelhan.

I’m trying to follow your advice, but I’m having a little trouble integrating it well. This is because I also have characteristics for rooms, and the characteristicValues can’t exist without the parent object, be it a building or a room.

Variant #1:

One approach I considered was to create one resource that combines the “characteristicValues” for “building” and “room.” In my example, this has led to the current approach:

Endpoints:

/characteristicValues
/characteristicValues/{id}
/characteristicValues/{id}/relationships/characteristic
/characteristicValues/{id}/relationships/object
/characteristic/{id}
/buildings/{id}
/rooms/{id}

My response:

{
  "data": {
    "type": "characteristicValue",
    "id": "{id}",
    "attributes": {
      "value": "",
      "remark": ""
    },
    "relationships": {
      "characteristic": {
        "links": {
          "self": "/characteristicValue/{id}/relationships/characteristic",
          "related": "/characteristic/{id}"
        },
        "data": {
          "type": "characteristic",
          "id": "{id}"
        }
      },
      "object": {
        "links": {
          "self": "/characteristicValue/{id}/relationships/object",
          "related": "/buildings/{id}"
          // Or for rooms
          // "related": "/rooms/{id}"
        },
        "data": {
          "type": "building",
          "id": "{id}"
        }
      }
    }
  }
}

The characteristicValue is associated with the linked object, which can have a type of “room” or “building.” Is it common to have different types in one relationship? Is this a feasible approach?

Variant #2:

Since the object characteristicValue can’t exist without the parent-object, I thought instead of creating a subresource like:

Endpoints (e.g., building):

/buildings/{id}/relationships/…
/buildings/{id}/characteristicValues
/buildings/{id}/characteristicValues/{id}
/buildings/{id}/characteristicValues/{id}/relationships/characteristic

The subresource would have the same functionality (CRUD, relationships, included, etc.) as its parent resource. Would it also follow the JSON:API specs?

// /building/4
{
“data”: {
“type”: “building”,
“id”: “4”,
“attributes”: {
“name”: “Empire State Building”
},
“relationships”: {
“characteristics”: {
“links”: {
“self”: “buildings/1/relationships/characteristics”,
“related”: “buildings/1/characteristics”
},
“data”: [
{
“type”: “characteristic”,
“id”: “9”, // characteristic-id for number_desks
“attributes”: {
“value”: “5”
}
},
{ …}
]
}
}
},
“links”: {
“self”: “buildings/4”
}
}