我希望能够编写JSON模式代码,该代码允许一个属性的值依赖于另一个属性的值。
更具体地说,我有两个问题A和B。只有当问题A有特定答案时,问题B的答案才能为空。如果问题A没有答案,那么问题B的值必须为空。
例如
A: Do you like cars? Yes/No
B: What is your favourite car?
仅当问题A的答案为“是”时才能回答问题B,否则必须将其保留为空。
经过一些研究,我发现了这个Stack Overflow线程,该线程描述了枚举以及回答该问题的if-then-else方法。枚举非常接近我的需要,定义如下:
{
"type": "object",
"properties": {
"foo": { "enum": ["bar", "baz"] },
"bar": { "type": "string" },
"baz": { "type": "string" }
},
"anyOf": [
{
"properties": {
"foo": { "enum": ["bar"] }
},
"required": ["bar"]
},
{
"properties": {
"foo": { "enum": ["baz"] }
},
"required": ["baz"]
}
]
}
在上面,当Foo
的值为"Bar"
时,则需要Bar
属性。同样,值为"Baz"
。但是,我不需要将属性设置为必需,而是希望能够将属性的类型从null更改为string。或做一些事情使B的答案有效。
对此有何想法?
答案 0 :(得分:2)
您考虑了
让我们来总结一下
"questionA": {
"type": "object",
"properties": {
"answer": {
"type": "string",
"minLength": 1,
"enum": ["Yes", "No"]
}
}
}
"questionB": {
"type": "object",
"properties": {
"answer": {
"type": null,
}
}
}
"questions": {
"type": "object",
"properties": {
"A": {"$ref": "#/definitions/questionA"},
"B": {"$ref": "#/definitions/questionB"}
},
"if": {
"properties" : {
"A": {"enum": ["Yes"]}
}
},
"then": {
"B": //Type = string and min length = 1 <-- Unsure what to put here to change the type of QuestionB
}
如果我正确理解您的问题,那么您想要达到的效果是:
如果受访者喜欢汽车,请向他询问最喜欢的汽车并获取答案,否则不要打扰最喜欢的汽车(最好将答案设为空)。
正如Relequestual在他的评论中正确指出的那样,JSON Schema使其很难“重新定义”类型。此外,每个if-then-else内容本身都必须是有效的架构。
为了达到此效果,您可能需要考虑以下方法:
下面列出了一些解决您的情况的示例架构(兼容draft07)。架构下方还提供了一些说明。
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type" : "object",
"propertyNames" : {
"enum" : [
"questionA",
"questionB",
]
},
"properties" : {
"questionA" : { "$ref" : "#/questionA" },
"questionB" : { "$ref" : "#/questionB" },
},
"dependencies" : {
"questionA" : {
"$ref" : "#/definitions/valid-combinations-of-qA-qB"
}
},
"definitions" : {
"does-like-cars":{
"properties" : {
"questionA" : {
"properties" : {
"answer" : { "enum" : ["Yes","y"] }
}
},
"questionB" : {
"properties" : {
"answer" : {
"$comment" : "Here #/questionB/answer becomes a type:string...",
"$ref" : "#/questionB/definitions/answer-def/string"
}
}
}
},
"required" : ["questionB"]
},
"doesnt-like-cars" :{
"properties" : {
"questionA" : {
"properties" : {
"answer" : { "enum" : ["No","n"] }
}
},
"questionB" : {
"properties" : {
"answer" : {
"$comment" : "Here #/questionB/answer becomes a type:null...",
"$ref" : "#/questionB/definitions/answer-def/null"
}
}
}
}
},
"valid-combinations-of-qA-qB" : {
"anyOf" : [
{ "$ref" : "#/definitions/doesnt-like-cars" },
{ "$ref" : "#/definitions/does-like-cars" }
]
},
},
"examples" : [
{
"questionA" : {
"answer" : "Yes",
},
"questionB" : {
"answer" : "Ass-kicking roadster",
},
},
{
"questionA" : {
"answer" : "No",
},
"questionB" : {
"answer" : null,
},
},
{
},
],
"questionA" : {
"$id" : "#/questionA",
"type" : "object",
"propertyNames" : {
"enum" : ["answer"]
},
"properties" : {
"answer" : {"$ref" : "#/questionA/definitions/answer-def"}
},
"definitions" : {
"answer-def" : {
"$comment" : "Consider using pattern instead of enum if case insensitiveness is required",
"type" : "string",
"enum" : ["Yes", "y", "No", "n"]
}
}
},
"questionB" : {
"$id" : "#/questionB",
"$comment" : "Please note no properties definitions here aside from propertyNames",
"type" : "object",
"propertyNames" : {
"enum" : ["answer"]
},
"definitions" : {
"answer-def" : {
"string" : {
"type" : "string",
"minLength" : 1,
},
"null" : {
"type" : "null"
}
}
}
},
}
因为您的要旨是这样;-)更严重的是,这是因为:
在要点中,您将两个问题都定义为对象。可能有 它背后的正当理由,所以我一直保持这种方式(但是无论何时平放 可以使用属性列表,例如“ questionA-answer”, 我希望使用“ questionB-answer”,这样可以减少架构规则的嵌套, 因此更具可读性且易于创建)。
从您的问题和要点看来,这对您很重要 “ questionB / answer”为null而不是未通过验证 反对/忽略不相关的内容,因此我保持了
请注意,我已经为“ questionA”和“ questionB”创建了单独的子模式。这是我的个人喜好,没有什么可以阻止您将所有内容都放入主模式的“ definitions”模式中,但是我通常这样做是因为:
因为我们在这里处理“ type”:“ object”,所以我使用了“ propertyNames”关键字来定义允许的属性名称的架构(因为编程语言中的类通常具有静态属性集)。尝试在每个对象中输入此集合之外的属性-模式验证失败。这样可以防止对象中出现垃圾。如果不需要这种行为,只需从每个对象中删除“ propertyNames”模式。
诀窍是:不要预先定义属性类型和其他相关的架构规则。请注意,“ questionB”模式中没有“属性”模式。相反,我使用“ definitions”为“ questionB”对象中的“ answer”属性准备了两种可能的定义。我将根据“ questionA”答案值来使用它们。
一些对象,应该说明架构的工作方式。试一试答案值,属性的存在等。请注意,空对象也将通过验证,因为不需要属性(如您的要点),并且只有一个依赖项-如果出现“ questionA”,则显示“ questionB”必须也出现。
好的。 因此,主模式可以具有两个属性:
questionA(一个包含属性“ answer”的对象)
questionB(包含属性“ answer”的对象)
是否需要“#/ questionA”? ->不,至少要根据您的要旨。
是否需要“ questionB”? ->仅当出现“#/ questionA”时。要增加侮辱性伤害,请:-)“#/ questionB / answer”的类型和允许的值严格取决于“#/ questionA / answer”的值。
->我可以安全地预定义主要对象,问题对象的基础,并且需要定义依赖项
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type" : "object",
"propertyNames" : {
"enum" : [
"questionA",
"questionB",
]
},
"properties" : {
"questionA" : { "$ref" : "#/questionA" },
"questionB" : { "$ref" : "#/questionB" },
},
"dependencies" : {
"questionA" : {
"$comment" : "when questionA prop appears in validated entity, do something to enforce questionB to be what it wants to be! (like Lady Gaga in Machette...)"
}
},
"questionA" : {
"$id" : "#/questionA",
"type" : "object",
"propertyNames" : {
"enum" : ["answer"]
},
},
"questionB" : {
"$id" : "#/questionB",
"type" : "object",
"propertyNames" : {
"enum" : ["answer"]
},
},
}
请注意:我正在有意识地通过“ $ id”关键字为问题子方案设置相对基本引用,以便能够将架构拆分为多个较小的文件,并具有可读性。
->我可以安全地预定义“ questionA / answer”属性:类型,允许的值等。
"questionA" : {
"$id" : "#/questionA",
"type" : "object",
"propertyNames" : {
"enum" : ["answer"]
},
"properties" : {
"answer" : {"$ref" : "#/questionA/definitions/answer-def"}
},
"definitions" : {
"answer-def" : {
"$comment" : "Consider using pattern instead of enum if case insensitiveness is required",
"type" : "string",
"enum" : ["Yes", "y", "No", "n"]
}
}
},
注意::我使用“定义”来定义特定属性的架构。以防万一我需要在其他地方重用该定义...(是的,对我的偏执)
->我不能如上所述安全地预定义“#/ questionB / answer”属性,必须在“#/ questionB”子模式中执行“技巧”部分
"questionB" : {
"$id" : "#/questionB",
"$comment" : "Please note no properties definitions here aside from propertyNames",
"type" : "object",
"propertyNames" : {
"enum" : ["answer"]
},
"definitions" : {
"answer-def" : {
"string" : {
"type" : "string",
"minLength" : 1,
},
"null" : {
"type" : "null"
}
}
}
},
注意:是否看到“#/ definitions / answer-def”?有两个子节点:“#/ definitions / answer-def / string”和“#/ definitions / answer-def / null”。我目前尚不确定该怎么做,但我知道我绝对需要最后使用“#/ questionB / answer”属性模式进行杂耍的功能。
->我必须为两个答案的有效组合定义规则,因为“#/ questionB / answer”必须始终存在;我正在主模式中执行此操作,该模式使用问题子模式,因为这是对它们的限制,从逻辑上讲,它是定义此类规则的好地方。
"definitions" : {
"does-like-cars":{
"properties" : {
"questionA" : {
"properties" : {
"answer" : { "enum" : ["Yes","y"] }
}
},
"questionB" : {
"properties" : {
"answer" : {
"$comment" : "Here #/questionB/answer becomes a type:string...",
"$ref" : "#/questionB/definitions/answer-def/string"
}
}
}
},
"required" : ["questionB"]
},
"doesnt-like-cars" :{
"properties" : {
"questionA" : {
"properties" : {
"answer" : { "enum" : ["No","n"] }
}
},
"questionB" : {
"properties" : {
"answer" : {
"$comment" : "Here #/questionB/answer becomes a type:null...",
"$ref" : "#/questionB/definitions/answer-def/null"
}
}
}
}
},
"valid-combinations-of-qA-qB" : {
"anyOf" : [
{ "$ref" : "#/definitions/doesnt-like-cars" },
{ "$ref" : "#/definitions/does-like-cars" }
]
},
所以有些人喜欢汽车-我基本上定义了“#/ questionA / answer”的允许值和“#/ questionB / answer”的属性的相关定义。由于这是架构,因此两个集合必须匹配才能满足此定义。请注意,为了不验证针对模式仅包含“ questionA”属性键的JSON,我已按要求将“ questionB”属性键标记为必需。
我为那些不喜欢汽车的人做过类似的事情(怎么可能不喜欢汽车?邪恶的时代...),最后我在“ valid-combinations-qa-qB”中说:要么,要么人。您要么喜欢汽车并给我答案,要么您不喜欢汽车并且答案必须为空。 “ XOR”(“ oneOf”)会自动出现,但是由于我已经定义了 like汽车AND answer 和不喜欢cars AND answer = null 作为完整模式,逻辑OR完全足够->“ anyOf”。
最后,画龙点睛的是在主架构的“依赖项”部分中使用该规则,该规则翻译为:如果“ questionA”出现在经过验证的实例中,则...或...
"dependencies" : {
"questionA" : {
"$ref" : "#/definitions/valid-combinations-of-qA-qB"
}
},
希望它可以澄清您的情况并为您提供帮助。
为什么不使用对象“答案”具有反映每个问题答案的属性,并使用键来标识问题?就答案之间的依赖性而言,它可以简化一些规则和参考(较少键入,是的,我是个懒惰的家伙)。
为什么“#/ questionB /答案”必须为空,而不是如果“#/ questionA / answer”为:{“枚举”:[“否”]}为空?
请参阅“了解JSON模式”:https://json-schema.org/understanding-json-schema/index.html
一些基本示例:https://json-schema.org/learn/
JSON模式验证参考:https://json-schema.org/latest/json-schema-validation.html
大量StackOverflow问答对如何使用JSON模式管理不同案例提供了很好的见识。
有时,检查相对的JSON指针RFC也可能会有所帮助。