For this JSON API design problem, I have an arbitrary set of key-value pairs that need to be provided in the API Request/Response bodies. Both the keys and values are unknown. What is the best way to structure this?
As far as I can tell, there are two ways to accomplish this:
1. Undocumented object keys
{
"fruit": "Apple",
"sport": "Hockey",
...
"keyN": "valueN"
}
PROS: Very clean, easy for application logic to parse
CONS: This object can't be documented properly - the shape of the object is infinitely arbitrary.
2. Array of Objects
[
{
"key": "fruit",
"value": "Apple"
},
{
"key": "sport",
"value": "Hockey"
},
...
{
"key": "keyN",
"value": "valueN"
}
]
PROS: Easy to document and understand as an array of objects with a known structure.
CONS: Application logic will be more verbose.
What is the best way to structure this?
NOTE: This is a question about API documentation, not about application logic. As noted above, I'm well aware that #1 is the best solution for manipulation in code. But it's unclear to me just yet how to document this in API docs in such a way that will always be interpreted correctly
I very, very strongly recommend #1. #2 is a perversion. Your data structure is a dictionary. Don't use an array to implement a dictionary, but with half the features.
Think about it: You say you cannot document #1. So how are you going to document #2? If #1 mustn't contain a key "strawberry", how do you document that #2 mustn't contain a dictionary with a (key, value) pair of key = "key", value = "strawberry"?
How do you check whether #1 or #2 contains a key "fruit", and what the value is? #1 is a direct access. dict ["fruit"]. In #2 you need to iterate through the array elements, check that they are all dictionaries, check if there is one with an entry key: "fruit", check that it has another entry "value". Maybe if you are paid by lines of code you would do that.
Funny enough, three completely different answers, each with a downvote. Obviously at least two downvoters are stupid.
Personally I don't see much difference between #1 and #2 in terms of documentation. If the fields are unknown then an empty map/object vs an empty array doesn't really matter.
I've seen this referred to as a 'Json Junk Drawer'.
One addition I have seen is a named map/object specific for the 'Json Junk Drawer' like:
{
"fieldsThatDontChange" : "example",
"attributes" :{
"unknownfield" : "unknown"
}
}
It's all an anti-pattern really. But one small benefit here is you can tell clients that the attributes section is where to put their undocumented stuff. Maybe even enforce client specific schemas on the attributes section if that's a use-case for example.
Here is some more info on JSON junk drawers which has some links to youtube videos discussing the subject:
http://apievangelist.com/2015/01/21/rest-api-design-bridging-what-we-have-to-the-future-by-organizing-the-json-junk-drawer/
The better solution is:
2. Array of Objects
[
{
"key": "fruit",
"value": "Apple"
},
{
"key": "sport",
"value": "Hockey"
},
...
{
"key": "keyN",
"value": "valueN"
}
]
Arrays are exactly the correct pattern to use when there is arbitrary length involved. This is more verbose but easier to understand. Favour verbosity.
Related
I use JSON Schema to describe my data structure
My data contains specific arrays of the following structure:
{
"string1",
"string2",
...
"stringN",
{
"object": "as",
"last": "item"
}
}
I'm wondering how to describe this in json schema (especially the thing "last item is an object")
If I knew the number of string items, "prefixItems" would do the thing (but there could be any number of them).
If the object was the first (not last) item, "prefixItems" together with "items" would work.
If is use "contains", it only checks the object is somewhere in my array, not checking it is the last item.
Seems that I need something like "reversePrefixItems", if such option existed - but it doesn't exists.
So, what is a proper way to describe the last item of an array? (and optionally all the preceding ones - knowing their type but not their total count)
there has been discussion of a proposed keyword postfixItems but it has not yet made it into the spec. I don't think there is a way to do this currently.
For ease of notation, I will use JSON in the following, though the anti-pattern can be programmed in many languages
Let's say that I have a sensible JSON such as
{
"SomeProperty": "SomeValue",
"SomeOtherProperty": 42,
"Items": [
"ValueOfItem0", "ValueOfItem1"
]
}
It has simple entries and an array of items. An alternative way of representing the data, which I think is an anti-pattern and for which I search the name is
{
"someProperty": "someValue",
"someOtherProperty": 42,
"Item0": "valueOfItem0",
"Item1": "valueOfItem1",
"NumberOfItems": 2
}
Instead of by the array, the items are kept 'together' by the keys, which a consumer of the anti-pattern JSON would need to predict. For this reason, the NumberOfItems property has been added, though by using a TryGet-like technique, the property can be made obsolete. Why would anyone do this? Limitations of the serializer.
Comments:
My search has revealed nothing. The sort-of opposite direction is the "Arrject", therefore my humble suggestion for the described anti-pattern, if yet unnamed, would be "Orray", a name already used by Star Wars.
I've already posted this question to the amazon developer forum but don't receive an answer there. I guess Stackoverflow should've been the first choice from the beginning:
From my understanding if I use a Custom Slot Type even if the list of its possible values does not contain the spoken word the spoken word is still passed to the function. The documentation says
A custom slot type is not the equivalent of an enumeration. Values outside the list may still be returned if recognized by the spoken language understanding system.
Now I have a Custom Slot Type LIST_OF_PERSONS with values Matthias|Max and an utterance of
EmployeeDetailsIntent {Person}
If I call this intend with a value not in LIST_OF_PERSONS the Intent still gets called but the JSON does not contain a "value" key for the Slot:
"request": {
"type": "IntentRequest",
"requestId": "EdwRequestId.a943e233-0713-4ea5-beba-d9287edb6083",
"locale": "de-DE",
"timestamp": "2017-03-09T14:38:29Z",
"intent": {
"name": "EmployeeDetailsIntent",
"slots": {
"Person": {
"name": "Person"
}
}
}
}
Is this "works as designed" or a bug? How do I access the spoken word in the Intent then? As this.event.request.intent.slots.Person.value is undefined?
My code lives in AWS lambda and I'm using the nodejs alexa-sdk Version 1.0.7. The language of my Skill is German.
(disclaimer: this post summarises my own "workaround". It might or might not be the "best way". Seems to have worked for me so thought I would share / document it here briefly)
I've recently bumped into similar issues for an utterance that looks like this:
"tell me about {townName}"
If I say "tell me about London", it works.
If I say "tell me about" (deliberately missing a {townName}), the program "dies" (and returns a JSON looking similar to your one, with undefined this.event.request.intent.slots.townName.value)
Though I'm not 100% sure whether this is meant to be a "feature" (i.e. we need to write smarter code to work around this) or "problem" (i.e. Alexa team needs to address or fix). This scenario has caused a real issue when it came to the certification process for me recently.
To get through this, I've implemented a workaround (or fix, whatever you call it) to avoid Alexa from "dying" as a result of this edge case.
From the Alexa skill-sample-nodejs-trivia index.js file, I've found a snippet function that helped me work around this (I've edited it a bit for my example for simplicity):
function isAnswerSlotValid(intent) {
var answerSlotFilled = intent && intent.slots &&
intent.slots.townName && intent.slots.townName.value;
return answerSlotFilled
}
(i.e. this function returns True for valid values for the slot townName and and False for undefined / otherwise).
When it comes to defining the intent, I could use this function to "get around" an empty slot value scenario:
var startHandlers = Alexa.CreateStateHandler(states.START,{
// bla bla bla//
"AnswerIntent": function() {
// handel missing slot value
var answerSlotValid = isAnswerSlotValid(this.event.request.intent);
if (answerSlotValid && moreConditions) {
// do something fun
}
else {
// handle empty slot scenario
}
}
// bla bla bla//
}
Would be interested to see if there are better / more "proper" solutions to this to handle empty / undefined slots more elegantly.
I have seen this happen when an intent has both utterances with and without a slot. For example:
myIntent what makes a car go fast
myIntent what makes a {CAR_TYPE} go fast
where CAR_TYPE has a list of different types of cars.
myIntent still needs to define the slot CAR_TYPE for the myIntent in the schema, but the first intent doesn't use it.
In this case, it might be best to include 'car' in CAR_TYPE and eliminate the first utterance. In other cases though, the sentence grammar really doesn't permit it, so you need to expect an empty slot like you're seeing.
I believe the issue is that utterances using a Custom Slot Type must be used with at least one other value.
For example
EmployeeDetailsIntent get {Person}
will work, while
EmployeeDetailsIntent {Person}
will not
Had same thing, get custom slot name, but value was missing in Json Input. Worked with normal slots. Using Alexa Developer Console. Had to add an "ID" value to my custom slot values and then I started getting values. I thought I had tried that before but maybe I had not put an ID on each value. That did the trick for me though where the other answers did not work for me.
I'm using backbone relational for my collection handling.
I have a complex object which may have duplicated ids inside. e.g.
{
id: "things/1",
children: [
{
id: "things/2",
children: [
{
id: "things/3",
children: null
}
]
},
{
id: "things/4",
children: [
{
id: "things/3",
children: null
}
]
},
]
}
I then try and use this as a relational collection, like so. (written in TypeScript).
constructor(options?) {
// ...
this.idAttribute = 'Id';
this.relations = [{
type: Backbone.HasMany,
key: 'Children',
relatedModel: 'Application.Models.MyModel',
collectionType: 'Backbone.Collection'
}
];
super(options);
}
As soon as I get duplicate ids from the server, however, BBR angrily throws an exception and things just don't happen. "Duplicate id!"
Should I be perhaps generating some sort of fake id based on a guid for these models? Or is there a way to tell the Backbone Relational store to stop enforcing this rule? Maybe I can just turn off the store altogether.
I'm not using this to do any collection fetches, fetch relateds, or anything like that. I'm really using it as a nice way of parsing a recursive data structure.
I've ran into this problem often when writing Jasmine tests as well, but managed to get around it by adding a random *10 multiplyer for each test to make sure the ids are different. But it's a pain in the neck to have to do this. So hopefully any fixes here will help me in unit testing as well.
I'm not averse to trying a different framework, but some models in my project use BBR, so it'd need to play nice. If there's something else out there that'd be more appropriate, feel free to suggest it, too.
Your data structure implies a strict tree-like relationship, while the data clearly isn't organized like that. Either make your data an actual tree, where each node is unique, or represent it with a structure that can handle more complex relationships.
I would suggest you just send the objects as a flat array, where each node has a childrenIds array. Then you can easily restore the children arrays after receiving the objects.
My eventual answer to this was to move to Backbone Associations. After writing a d.ts file (available on the DefinitelyTyped repository) and some initial refactoring to change the relations blocks, things pretty much work off the bat! The only thing you need to remember is to set any collections by default to an empty array in the defaults function of your model. Hope this helps someone!
I'm starting with this JSON-LD document (json-ld playground), where the meat looks like this:
"from": [
"protein:15718680",
"protein:157427902"
],
"protein_gene": [
"gene:522311",
"gene:3702"
]
Now, the "protein_gene" predicate always takes gene identifiers as values. Since there can be hundreds of these, what I really want in the JSON is this:
"from": [
"protein:15718680",
"protein:157427902"
],
"protein_gene": [
"522311",
"3702"
]
without the "gene" prefix everywhere. Is it possible? The closest I got, based on this SO question, was this (json-ld playground). It causes the predicate values to be expanded into IRIs, and not relative IRIs, but those based on the #vocab. But it is not right, because I want a predicate-specific vocab.
I also know that I can play tricks with embedded (local) contexts, and I got this (json-ld playground) to work, but it is still uglier than I would like.
There are a couple of ways to do this for a single property, but not multiple (more than two, you can use #vocab and #base if you have two). Apart from injecting contexts, there's no way to scope #vocab and #base to a property.