上下文
我们正在为web(HTML+JS(和移动设备(iOS/Android/Windows(构建JSON API。
服务器需要发送具有基本结构和可变结构的数据。在我们的示例中,基本结构包括"name"one_answers"description",变量结构称为"template",根据其类型有不同的字段。我们至少找到了三种写它的方法(可能还有更多(:
A: 在对象外部定义的可变结构类型
{
"id": "A001",
"name": "My First Game",
...,
"template_type": "BATTLE",
"template": {
...
}
}
在这种情况下,客户端应该查看"template_type",以确定如何解析"template"。"模板"对象本身并不能自给自足地知道它是什么
B: 对象内部定义的变量结构类型
{
"id": "A001",
"name": "My First Game",
...,
"template": {
"type": "BATTLE",
...
}
}
在这种情况下,客户端应该查看"template"中的"type",以确定如何解析"template(模板("。"模板"对象本身就可以自给自足地知道它是什么
C: 由对象的键定义的可变结构类型
{
"id": "A001",
"name": "My First Game",
...,
"template_battle": {
...
}
}
在这种情况下,客户端应该查看所有的密钥("template_baight"、"template_bigue"…(,以确定我们有哪种类型的游戏。单独的"template_waight"对象可以自给自足地知道它是什么,因为它总是"battle"类型。
问题
关于哪种JSON解决方案最适合web和移动设备解析和使用,有什么建议吗?(您可以提出其他解决方案(
就我个人而言,我会将类型放在模板本身上,原因很简单,那就是封装。想象一下,您想要将模板和外部对象的创建分开(记住关注点的分离和单一责任原则(https://en.wikipedia.org/wiki/Single_responsibility_principle))。如果类型在外部对象上,则必须始终指定模板的类型才能创建它。这是可能的,但它会增加耦合并违反封装。
为了进一步阅读,我建议https://en.wikipedia.org/wiki/SOLID_(面向对象的设计(。
我建议选择选项A,原因很简单。
A>B,因为单独的数据和类型:
它将类型信息与数据本身分离。通过这样做,如果你想要一个template_type
属性与之关联,你就不会有命名冲突。你可以简化它,枚举所有属性,并在自定义对象上设置它们,而不必使用特殊情况来忽略类型属性。
A>C,因为工作量较少:
分析密钥字符串需要更多的工作。要首先找到template_*
键,您需要枚举这些属性并在它们上循环以找到所需的属性。
最终,我认为选项A将为您提供解析和使用数据的最简单方法。
IMHO方法B会更好。这只是因为,它为用户提供了一种通用方法来访问模板的属性,而不必考虑其类型。通过这种方式,用户可以简单地为通用模板编写程序,该模板将类型作为其自身的属性。
例如,假设您有一个名为Template的对象类型,它映射模板到Java对象的json定义。
Class Template{ String type; String attribute1; String attribute2; ...... ...... }
通过使用方法B,您可以直接映射模板,到上面的模板对象。(在这种情况下,它是一个Java对象但该概念当然适用于任何其他编程语言(。
用户不需要事先了解模板类型访问模板的定义。这就是为什么它被称为更通用的方法。
我更喜欢你的B变体,而不是A和C。
然而,您也可以考虑这样的结构:
{
"longDesc": "The Long description and other(?)necessary hints here",
"type": "template",
"ID": {
"A001": {
"name": "My First Game",
"type": "BATTLE"
/*more data here*/
},
"A002": {
"name": "My 2nd Game",
"type": "STRATEGY"
/*more data here*/
}
}
};
它在日常使用中可能会给人更好的感觉。
我更喜欢B而不是其他,因为它将分离关注点/数据。
因为在这里,如果你只想处理模板数据,你可以在B(例如Obj.template(的情况下一步轻松提取模板数据,但在A的情况下这并不容易。
此外,如果你将来添加多种类型的模板,那么如果你想提取模板数据,在B的情况下(例如对象模板(是直接的,但在C情况下,你需要编写如下代码,
if(template_type='temp1'){
template=Obj["template_tep1"]
}
if(template_type='temp1'){
template=Obj["template_tep1"]
}
if(template_type='temp1'){
template=Obj["template_tep1"]
}
或
you need to write code like template=Obj["template"+Obj.template_type].
所以比起其他人,我更喜欢B。
B:使用独立的json节点更容易
正如其他答案所说,出于封装的原因,我会选择B,但我会给出另一个务实的理由:想想你自己开发的通用流程会做什么,或者如果你使用库:我会使用"Jackson"(似乎可以在Android上使用它(。
- 如果类型在您解析的JsonNode之外,则必须在反序列化程序中为每个属性指定类型所在的位置;如果类型在同一Node内部,则仅指定类型"在对象内部"的位置,并且对于许多对象,它可以是相同的
- 另一个参数是,如果只传递"Battle"对象,它没有容器,因此没有外部属性来指定类型
- 其他参数,至少有1个JS库使用此技术:ExtJS,请参阅文档中的"xtype"属性http://docs.sencha.com/extjs/5.0.1/guides/getting_started/getting_started.html
因此,这里是您想要用好的类型解析的节点:
{
"type": "BATTLE",
"aPropertyOfBattle":1
}
这是这个的jackson代码
@JsonTypeInfo (use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "type")
@JsonSubTypes ({
@JsonSubTypes.Type (Battle.class),
//...all your types
})
public interface ICustomType {}
@JsonTypeName("BATTLE")
public class Battle implements ICustomType{
int aPropertyOfBattle;
// getters/setters...
}
jackson提供了一种"粘贴"类型的解决方案:
完整工作代码:
@JsonTypeInfo (use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "type")
@JsonSubTypes ({
@JsonSubTypes.Type (Battle.class),
//...all your types
})
public interface ICustomType {}
@JsonTypeName("BATTLE")
public class Battle implements ICustomType{
int aPropertyOfBattle;
// getters setters...
}
public class BattleContainer {
private ICustomType template;
private String id;
private String name;
// getters/setters
}
public class BattleTest {
@Test
public void testBattle() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
ICustomType battle = objectMapper.readValue("{'type': 'BATTLE','aPropertyOfBattle':1}".replace(''','"'),Battle.class );
Assert.assertTrue("Instance of battle",battle instanceof Battle);
Assert.assertEquals(((Battle)battle).getaPropertyOfBattle(),1);
}
@Test
public void testBattleContainer() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
BattleContainer battleContainer = objectMapper.readValue("{'id': 'A001','name': 'My First Game','template': {'type': 'BATTLE', 'aPropertyOfBattle':1}}"
.replace(''','"'),BattleContainer.class );
Assert.assertTrue("Instance of battle",battleContainer.getTemplate() instanceof Battle);
Assert.assertEquals(((Battle)battleContainer.getTemplate()).getaPropertyOfBattle(),1);
}
}
请注意,这不是特定于jackson的,您可以使用java中的简单JsonNode来解析节点。
编辑:由于我给出了一个技术解决方案,所以我认为这似乎不符合主题,所以我准确地说,这里的论点不是使用Jackson,而是表明无论你选择什么解决方案语言和库,都有可能以优雅的方式使用"B"解决方案。
D:封装节点另一个解决方案是:
{
"BATTLE":{
"aPropertyOfBattle":1
}
}
解析可能更容易:获取属性名称,然后使用任何工具(Gson或其他…(解析子节点在jackson中,唯一的区别是使用include = As.WRAPPER_OBJECT
不便之处在于,在Javascript中使用它的逻辑性较差,因为在结构的中间有一个无用的节点。
Jackson的其他解决方案
此库作为include = As....
后面的其他选项
- 助理助理
As.EXTERNAL_PROPERTY
WRAPPER_ARRAY
也更容易解析,但我觉得它不优雅(它完全是主观的(:[ "BATTLE", { "aPropertyOfBattle":1 } ]
CCD_ 6将是A解决方案,但正如我所说,每次使用变量时都必须指定它,而不是在类型类中指定它(对我来说,它缺乏连贯性,因为你可以在不同的上下文中使用"Battle"对象,使用不同的类型命名约定(
@JsonTypeInfo (use = JsonTypeInfo.Id.NAME, include = As.EXTERNAL_PROPERTY, property = "template_type") private ICustomType template;
再次注意,我受到了jackson功能的启发,但它可以应用于每种语言的每种解决方案。
我建议在两个不同的集合中使用静态和动态结构
如下图所示
静态结构并使用动态字段作为数组,并通过传递唯一id或您认为可能是独特的字段 动态结构。在动态结构中,您可以将其余字段传递到不同的api中,因为主要功能(如搜索、自动完成(可能依赖于它们。类似地,父api也可以很容易地引用它。 这也允许更快的搜索、索引和良好的压缩。动态结构有助于减少开销,而不是遍历整个单个JSONAPI来搜索相关的标记。 这种方法还有很多其他主要用途,但我只提到了其中的一些,我认为这些用途将有助于您以这种方式进行设计。{
"id": "A001",
"name": "My First Game",
"description" : "GGWP noob",
...,
"template": ['temp1','temp2','temp3','temp4'],
}
{
"id" : "temp1",
"type": "BATTLE",
... //other features
}