(left: InferredShape, right: InferredShape)
| 83 | }; |
| 84 | |
| 85 | const mergeObjectShapes = (left: InferredShape, right: InferredShape): InferredShape => { |
| 86 | // A map-shaped observation absorbs struct-shaped ones: once keys look like |
| 87 | // data, later struct keys are data too. |
| 88 | if (left.additionalProperties !== undefined || right.additionalProperties !== undefined) { |
| 89 | const values = [ |
| 90 | left.additionalProperties, |
| 91 | right.additionalProperties, |
| 92 | ...Object.values(left.properties ?? {}), |
| 93 | ...Object.values(right.properties ?? {}), |
| 94 | ].filter((shape): shape is InferredShape => shape !== undefined); |
| 95 | return { |
| 96 | type: "object", |
| 97 | additionalProperties: |
| 98 | values.length === 0 ? UNKNOWN : values.reduce((a, b) => mergeShapes(a, b)), |
| 99 | }; |
| 100 | } |
| 101 | |
| 102 | const leftProps = left.properties ?? {}; |
| 103 | const rightProps = right.properties ?? {}; |
| 104 | const keys = [...new Set([...Object.keys(leftProps), ...Object.keys(rightProps)])].sort(); |
| 105 | if (keys.length > MAX_OBJECT_KEYS) { |
| 106 | const values = keys |
| 107 | .slice(0, MAX_ARRAY_SAMPLE) |
| 108 | .map((key) => leftProps[key] ?? rightProps[key]) |
| 109 | .filter((shape): shape is InferredShape => shape !== undefined); |
| 110 | return { |
| 111 | type: "object", |
| 112 | additionalProperties: |
| 113 | values.length === 0 ? UNKNOWN : values.reduce((a, b) => mergeShapes(a, b)), |
| 114 | }; |
| 115 | } |
| 116 | |
| 117 | const properties: Record<string, InferredShape> = {}; |
| 118 | for (const key of keys) { |
| 119 | const a = leftProps[key]; |
| 120 | const b = rightProps[key]; |
| 121 | properties[key] = a !== undefined && b !== undefined ? mergeShapes(a, b) : (a ?? b ?? UNKNOWN); |
| 122 | } |
| 123 | const leftRequired = new Set(left.required ?? []); |
| 124 | const required = (right.required ?? []).filter((key) => leftRequired.has(key)).sort(); |
| 125 | return { type: "object", properties, required }; |
| 126 | }; |
| 127 | |
| 128 | /** |
| 129 | * Merge two observed shapes into the narrowest shape matching both. |
no test coverage detected