Merge
Merge overlays the body on the entity, with PATCH /entities/{id}. Members you name change, and everything else stays. It answers 204.
- http
- curl
- rust
- python
PATCH /ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A4567
Content-Type: application/json
{
"speed": {
"type": "Property",
"value": 65
},
"colour": "urn:ngsi-ld:null"
}
curl -i -X PATCH http://localhost:1056/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A4567 \
-H 'Content-Type: application/json' \
-d '{
"speed": {
"type": "Property",
"value": 65
},
"colour": "urn:ngsi-ld:null"
}'
let fragment = EntityFragment::builder()
.property("speed", 65)
.delete("colour")
.build()?;
let outcome = client.entities().merge(&id, &fragment).send().await?;
response = requests.patch(
ENTITY,
json={
"speed": {
"type": "Property",
"value": 65,
},
"colour": "urn:ngsi-ld:null",
},
)
assert response.status_code == 204
HTTP/1.1 204 No Content
This sets speed and deletes colour. In plain JSON Merge Patch, a null deletes a member. NGSI-LD uses the NGSI-LD Null, urn:ngsi-ld:null, instead, and Andromeda refuses a JSON null anywhere in a body. Strictness has the rules for both. In Rust, delete writes the NGSI-LD Null for you.
Merge descends
A merge also reaches inside a value. A partial attribute update of address would replace its whole value, so setting only street would drop the city and country beside it. Sent as a merge, the same change keeps them:
- http
- curl
- rust
- python
PATCH /ngsi-ld/v1/entities/urn:ngsi-ld:Building:1
Content-Type: application/json
{
"id": "urn:ngsi-ld:Building:1",
"type": "Building",
"address": {
"type": "Property",
"value": {
"street": "Pariser Platz",
"country": "urn:ngsi-ld:null"
}
}
}
curl -i -X PATCH http://localhost:1056/ngsi-ld/v1/entities/urn:ngsi-ld:Building:1 \
-H 'Content-Type: application/json' \
-d '{
"id": "urn:ngsi-ld:Building:1",
"type": "Building",
"address": {
"type": "Property",
"value": {
"street": "Pariser Platz",
"country": "urn:ngsi-ld:null"
}
}
}'
let address = ValuePatch::new()
.set("street", "Pariser Platz")
.delete("country");
let fragment = EntityFragment::for_entity(iri!("urn:ngsi-ld:Building:1"), "Building")
.patch("address", Attribute::from(Property::merge_builder(address).build()?))
.build()?;
let outcome = client.entities().merge(&building, &fragment).send().await?;
response = requests.patch(
BUILDING,
json={
"id": "urn:ngsi-ld:Building:1",
"type": "Building",
"address": {
"type": "Property",
"value": {
"street": "Pariser Platz",
"country": "urn:ngsi-ld:null",
},
},
},
)
assert response.status_code == 204
HTTP/1.1 204 No Content
The merge walks into address, then into its value. street is set, country is deleted by its NGSI-LD Null, and city is never mentioned, so it stays. That descent is the whole point of merge.
The same rule holds one level out. A merge naming an attribute the entity does not have adds it, leaving every other attribute alone. A merge naming an existing attribute with only a new value replaces that value and keeps the attribute's unitCode and observedAt, because the merge never leaves the member it descended into.
The NGSI-LD Null deletes exactly the member it sits on, however deep. A null observedAt removes only the observedAt, a null unitCode only the unit, and a null on a sub-Relationship's object removes the whole sub-Relationship. Nothing beside them moves.
Only the exact URN urn:ngsi-ld:null deletes. An address that merely looks like it, https://uri.etsi.org/ngsi-ld/null, is ordinary data. Sent as a Property value or a Relationship object it is stored as written, and reads back as written. A JSON null is refused everywhere in a request body.
What gets refused
400BadRequestDatafor a merge fragment that tries to change adatasetId.404ResourceNotFoundfor a fragment against an attribute path the entity does not have. Merge adds attributes at the entity level, but a fragment against a missing attribute path is still not found.400BadRequestDatafor a JSONnullanywhere in the body. Deletion is the NGSI-LD Null.- Everything in the common list.