function applyProperties(props, object, conflicts, updated) {
  if (!props) return

  for (let key of Object.keys(props)) {
    const values = {}, opIds = Object.keys(props[key]).sort(lamportCompare).reverse()
    for (let opId of opIds) {
      const subpatch = props[key][opId]
      if (conflicts[key] && conflicts[key][opId]) {
        values[opId] = getValue(subpatch, conflicts[key][opId], updated)
      } else {
        values[opId] = getValue(subpatch, undefined, updated)
      }
    }

    if (opIds.length === 0) {
      delete object[key]
      delete conflicts[key]
    } else {
      object[key] = values[opIds[0]]
      conflicts[key] = values
    }
  }
}
`props` is an object of the form:
`{key1: {opId1: {...}, opId2: {...}}, key2: {opId3: {...}}}`
where the outer object is a mapping from property names to inner objects,
and the inner objects are a mapping from operation ID to sub-patch.
This function interprets that structure and updates the objects `object` and
`conflicts` to reflect it. For each key, the greatest opId (by Lamport TS
order) is chosen as the default resolution; that op's value is assigned
to `object[key]`. Moreover, all the opIds and values are packed into a
conflicts object of the form `{opId1: value1, opId2: value2}` and assigned
to `conflicts[key]`. If there is no conflict, the conflicts object contains
just a single opId-value mapping.