openAPI-specification: what is the type of the variable when one of them is given in the AdditionalProperties? - dictionary

I am new to OpenAPI-specifications and I was confused about the type of a variable when the attribute additionalProperties is used.
For example at https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schemaObject we see the example
{
"type": "object",
"additionalProperties": {
"type": "string"
}
}
What is the type of this now? Is it a string or an object? I do not see how it can be both of them.
thanks in advance, Koen

In this context, the additionalProperties keyword is used to define dictionaries/maps. type: object represents the dictionary itself, and the additionalProperties keyword defines the type of values in the dictionary. The key type is not mentioned because the keys are always strings.
A more general explanation is that additionalProperties is used to allow or deny extra properties not explicitly defined in the properties and patternProperties sections. If extra properties are allowed, additionalProperties specifies their value type.
The example is your post represents a simple string-to-string dictionary, or an object with arbitrary properties whose values are strings, such as:
{
"foo": "bar",
"hello": "world"
}
Similarly, a string-to-integer dictionary is defined as:
{
"type": "object",
"additionalProperties": {
"type": "integer"
}
}
This schema represents objects such as:
{
"apples": 3,
"oranges": 5
}
If the additionalProperties keyword does not specify a type, this means a free-form object with arbitrary properties and values.
{
"type": "object",
"additionalProperties": {}
}
// In OpenAPI 3.x, this can also be written as
{
"type": "object",
"additionalProperties": true
}
For additional info and examples, see:
Dictionaries, HashMaps and Associative Arrays on swagger.io
Additional Properties in the Understanding JSON Schema guide
Why additionalProperties is the way to represent Dictionary/Map in OpenAPI
Swagger Editor dictionary parameter definition
Swagger complex response model with dynamic key value hash maps

Related

Composite index for optional field in Cosmos

I have a collection in Cosmos DB which contains documents of different types (and schemas):
{
"partKey": "...",
"type": "type1",
"data": {
"field1": 123,
"field2": "sdfsdf"
}
}
{
"partKey": "...",
"type": "type2",
"data": {
"field3": ["123", "456", "789"]
}
}
I'm trying to create a composite index [/type, /data/field3/[]/?], but faced an issue:
The indexing path '\\/data\\/field3\\/[]\\/?' could not be accepted, failed near position '15'. Please ensure that the path is a valid path. Common errors include invalid characters or absence of quotes around labels
We don't support wildcards for Composite Indexes in Cosmos DB. Here is a composite index sample as reference.
We will update our docs to be more clear in this. I looked over these and we don't currently document this today.
Thanks.
In composite indexes, you just need to specify the paths that you want to index, rather than the values, so for your example:
"compositeIndexes":[
[
{
"path":"/type",
"order":"ascending"
},
{
"path":"/data/field3",
"order":"descending"
}
]
]
Just specify the order type you need for your queries (I've just used these ones as an example).
For different documents that have different properties underneath your data property, I believe you will have to add each composite index for each use case that you need since composite indexes don't support wildcards, so you would need to add:
/data/field1 /data/field2 etc etc
Hope this helps.

Is it possible to create a JSON Schema that can validate a hashmap / dictionary as opposed to an object?

So if I have an object, let's call it a Person, like:
{
"email": "foo#bar.com",
"first"" "foo",
"last": "bar"
}
This Person object can all be validated quite well with JSON Schema. The issue is when multiple of these are arranged into a dictionary where the email field is used as the key and the object is the value. For example:
{
"foo#bar.com": {
"email": "foo#bar.com",
"first"" "foo",
"last": "bar"
},
"you#your.com": {
"email": "you#your.com",
"first": "Bob",
"last": "Bobton"
},
"me#mine.com": {
"email": "me#mine.com",
"first": "Deb",
"last": "Debbington"
}
}
This is a common way to structure data. Beyond the validation of the Person values, which can be handled well by a JSON Schema, there are a number of validations that would be useful on the dictionary:
The key is an email and can be validated as one.
The value is always a Person.
The key is always identical to the value's email field.
All the keys are unique.
Is it possible to implement these dictionary validations using JSON Schema?
The first two requirements, yes.
You can use patternProperties.
The other two you ask are not possible using JSON Schema. Sorry.
My expectation would be you receive the data in the first form from an API, validate, then map reduce to your desired structure.

AppSync query resolver: are expressionNames and expressionValues necessary?

https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-dynamodb.html#aws-appsync-resolver-mapping-template-reference-dynamodb-query
AppSync doc says that expressionNames and expressionValues are optional fields, but they are always populated by code generation. First question, should they be included as a best practice when working with DynamoDB? If so, why?
AppSync resolver for a query on the partition key:
{
"version": "2017-02-28",
"operation": "Query",
"query": {
"expression": "#partitionKey = :partitionKey",
"expressionNames": {
"#partitionKey": "partitionKey"
},
"expressionValues": {
":partitionKey": {
"S": "${ctx.args.partitionKey}"
}
}
}
}
Second question, what exactly is the layman translation of the expression field here in the code above? What exactly is that statement telling DynamoDB to do? What is the use of the # in "expression": "#partitionKey = :partitionKey" and are the expression names and values just formatting safeguards?
Let me answer your second question first:
expressionNames
expressionNames are used for interpolation. What this means is after interpolation, this filter expression object:
"expression": "#partitionKey = :value",
"expressionNames": {
"#partitionKey": "id"
}
will be transformed to:
"expression": "id = :value",
the #partitionKey acts as a placeholder for your column name id. '#' happens to be the delimiter.
But why?
expressionNames are necessary because certain keywords are reserved by DynamoDB, meaning you can't use these words inside a DynamoDB expression.
expressionValues
When you need to compare anything in a DynamoDB expression, you will need also to use a substitute for the actual value using a placeholder, because the DynamoDB typed value is a complex object.
In the following example:
"expression": "myKey = :partitionKey",
"expressionValues": {
":partitionKey": {
"S": "123"
}
}
:partitionKey is the placeholder for the complex value
{
"S": "123"
}
':' is the different delimiter that tells DynamoDB to use the expressionValues map when replacing.
Why are expressionNames and expressionValues always used by code generation?
It is just simpler for the code generation logic to always use expressionNames and expressionValues because there is no need to have two code paths for reserved/non-reserved DynamoDB words. Using expressionNames will always prevent collisions!

Object must NOT have key

I am trying to define that an object must NOT have a certain key.
Here is my case:
alert({
items: [{ label:'Apple' }, { label:'Orange' }]
})
alert({
items: [{ foo:'Apple' }, { foo:'Orange' }]
labelKey: 'foo'
})
If items is an array of ojects that does not contain "label" key, then labelKey is required in Options
I tried this:
type Options = {|
items: Array<{ label:string }>
|} | {|
items: Array<$Diff<{}, { label:string }>>,
labelKey: string // must be key in items
|}
function alert(options: Options) {
}
Bonus question:
Also is it possible to define that labelKey is any key from objects passed in items?
Ensuring a property does not exist on an object
tl;dr: use { myProp?: empty }
I'm assuming you want to use the objects as maps when you pass something into the alert function. The trick to creating a map without a label is to give a property that, if assigned to something, will fail to typecheck.
We can leverage the empty type, a type which doesn't match against anything, to get the desired effect. It's a little tricky to use an empty type in conjunction with an object map, because by defining a property, we tell flow that we want that type to be in the object. So this fails to typecheck:
(Try)
type MapWithLabel = {
[string]: string,
label: string,
}
type MapWithoutLabel = {[string]: mixed, label: empty}
type Options = {|
items: Array<MapWithLabel>
|} | {|
labelKey: string,
items: Array<MapWithoutLabel>,
|}
declare function alert(options: Options): void;
alert({
items: [{ foo:'Apple' }], // Error, expected a "label" property with empty type
labelKey: 'foo'
})
Next, we can define the property as optional, which means to only typecheck against empty if the property exists. With this we can give the object a "label" property that either:
Does not exist OR
Has a type that matches nothing (empty)
So code can either not have a value for that property (what we want), or it can pass something that is empty (this isn't possible).
(Try)
type MapWithLabel = {
[string]: string,
label: string,
}
type MapWithoutLabel = {[string]: mixed, label?: empty}
type Options = {|
items: Array<MapWithLabel>
|} | {|
labelKey: string,
items: Array<MapWithoutLabel>,
|}
declare function alert(options: Options): void;
alert({
items: [{ label:'Apple' }],
})
alert({
items: [{ label:'Apple' }], // Error - Should not have label
labelKey: 'ohno',
})
alert({
items: [{ foo:'Apple' }],
labelKey: 'foo'
})
alert({
items: [{ foo:'Apple' }], // Error - Needs a labelKey
})
So to get the desired effect, we needed to leverage two tools: optional properties and the empty type. With it, we can specify an object that will fail to typecheck if that empty property exists.
Setting a dynamic property key at the type level
tl;dr: not possible
Regarding the bonus question: I'm not sure Flow could understand that since I don't know of a way to set a variable property on objects. I would not expect this feature since it could make things complicated/impossible to type check.
Edit: After a little more research, you can use indexer properties to assert an object has a key at the type level:
(Try)
type ObjWithKey<T: string = 'label'> = {
// An indexer property with only one valid value: T with "label"
// as default, but we can't ensure that the property exists anymore
// and multiple indexers are not supported.
[T]: string,
aNumber: 3,
aFunction: () => void,
}
declare var usesLabel: ObjWithKey<>
(usesLabel.label: string);
(usesLabel.aNumber: number);
(usesLabel.missing: number); //Error - Doesn't exist on object
(usesLabel.aFunction: () => void);
(usesLabel.aFunction: string); //Error - Wrong type
However, you can't do that and use the object as a general map, since multiple indexer properties are not supported (Try). For reference, someone else tried to do something else similar, but couldn't get it to work.
If that's a major problem for you, see if you can architect your data structure in a different way to make it easier for static analysis with Flow.

How to create GTM data layer variable with complex array

In Google Tag Manager a pre-defined variable type of "Data Layer Variable" exists with an input for the variable name. In a standard single level of key/value pairs this is easy.
var dataLayer = [{"mykey":"myvalue"}];
Given that data layer you'd just use mykey as your variable to input into GTM. However, if you use the CEDDL spec (http://www.w3.org/2013/12/ceddl-201312.pdf) structure you end up with a deeply nested array:
dataLayer = [
{
"product": [
{
"category": {
"primaryCategory": "Auto Loans"
},
"productInfo": {
"productID": "1",
"productName": "PurchaseLoan",
"description": "Auto finance loan"
},
"security": [
"Analytics",
"Personalization",
"Recommendations"
]
}
]
}
]
So the real question is: how do I access the value of "productName" in the above example?
In standard Javascript you might access it like so:
dataLayer[1].product[0].productInfo.productName
or
dataLayer.1.product.1.productInfo.productName
... but neither of these options work (with or without dataLayer.1 as the first node).
This is the UI to enter the variable name:
When you define your DataLayer variable in GTM, you don't need to specify "dataLayer" in the variable name, ie. it should just be:
product.0.productInfo.productName

Resources