JSON:更好地定义对象内部或外部的对象类型



上下文

我们正在为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或您认为可能是独特的字段

{
  "id": "A001",
  "name": "My First Game",
  "description" : "GGWP noob",
  ...,
  "template": ['temp1','temp2','temp3','temp4'],
}

动态结构。在动态结构中,您可以将其余字段传递到不同的api中,因为主要功能(如搜索自动完成(可能依赖于它们。类似地,父api也可以很容易地引用它。

  {
    "id"  : "temp1",
    "type": "BATTLE",
    ... //other features
  }

这也允许更快的搜索索引良好的压缩动态结构有助于减少开销,而不是遍历整个单个JSONAPI来搜索相关的标记

这种方法还有很多其他主要用途,但我只提到了其中的一些,我认为这些用途将有助于您以这种方式进行设计。

最新更新