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

时间:2016-11-02 09:03:13

标签: json oop data-structures

上下文

我们正在为Web(HTML + JS)和移动(iOS / Android / Windows)构建JSON API。

服务器需要发送具有基本结构和可变结构的数据。在我们的示例中,基础结构包括" name"和"描述",变量结构被称为"模板"并根据其类型有不同的字段。我们想出了至少三种写作方式(可能还有更多):

A:在对象

之外定义的变量结构类型
{
  "id": "A001",
  "name": "My First Game",
  ...,
  "template_type": "BATTLE",
  "template": {
    ...
  }
}

在这种情况下,客户应该查看" template_type"为了确定如何解析"模板"。 "模板"仅靠对象并不能自给自足以了解它是什么。

B:在对象

中定义的变量结构类型
{
  "id": "A001",
  "name": "My First Game",
  ...,
  "template": {
    "type": "BATTLE",
    ...
  }
}

在这种情况下,客户应该查看"输入"内部"模板"为了确定如何解析"模板"。 "模板"只有对象是自给自足的才能知道它是什么。

C:由对象的键

定义的变量结构类型
{
  "id": "A001",
  "name": "My First Game",
  ...,
  "template_battle": {
    ...
  }
}

在这种情况下,客户端应该查看所有键(" template_battle"," template_puzzle",...),以确定我们拥有哪种类型的游戏。 " template_battle"只有对象是自足的才能知道它是什么,因为它永远是" BATTLE"类型。

问题

有关哪种JSON解决方案对Web和移动设备最友好的解析和使用的建议? (你可以提出其他解决方案)

7 个答案:

答案 0 :(得分:8)

就个人而言,我会把这个类型放在模板本身上,原因很简单,那就是封装。想象一下,你想要分离模板和外部对象的创建(记住关注点的分离和单一责任原则(https://en.wikipedia.org/wiki/Single_responsibility_principle))。如果类型位于外部对象上,则始终必须指定模板的类型才能创建模板。这是一种可能性,但它增加了耦合并违反了封装。

如需进一步阅读,我建议https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)开头。

答案 1 :(得分:3)

我建议选择A,原因有两个。

A> B因为单独的数据和类型:

它将类型信息与数据本身分开。通过这样做,如果您想要一个与之关联的template_type属性,则不会出现命名冲突。您可以简化它枚举所有属性并在自定义对象上设置它们,而不必有一个特殊情况来忽略type属性。

A> C因为工作量少:

解析密钥字符串是更多的工作。要首先找到template_*键,您需要枚举属性并循环遍历它们以找到所需的属性。

最终,我认为选项A将为您提供最简单的解析和使用数据的方法。

答案 2 :(得分:2)

方法B会好得多恕我直言。这仅仅是因为它为用户提供了一种通用方法来访问模板的属性而不关心其类型。以这种方式,用户可以简单地为包括该类型作为其自身属性的通用模板编写他的程序。

  

例如,假设您有一个名为Template的对象类型进行映射   json将模板定义为Java对象。

Class Template{
  String type;
  String attribute1;
  String attribute2;

......
......
}
     

通过使用方法B,您可以直接映射它的json定义   模板,到上面的模板对象。(在这种情况下,它是一个Java对象   但当然这个概念适用于任何其他编程语言。)

     

用户之前不需要具有模板类型的先验知识   访问模板的定义。这就是为什么它被称为a   更通用的方法。

答案 3 :(得分:2)

我更喜欢你的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*/
      }
   }
};

在日常使用中可能会有更好的感觉。

答案 4 :(得分:2)

我希望 B 优先于其他人,因为它会将问题/数据分开。

因为在这里,如果你只想处理模板数据,你可以在 B 的情况下轻松地一步提取模板数据(例如Obj.template)但是 A不容易即可。

而且,如果您将来添加多种类型的模板,那么如果您想要提取模板数据,那么在 B (例如Obj.template)的情况下它是直接的,但是在< strong> 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 胜过其他人。

答案 5 :(得分:1)

建议您使用两个不同集合中的 static 动态结构

如下所示

静态结构并将动态字段用作数组并传递唯一ID 或字段您认为可能唯一

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

动态结构。在动态结构中,您可以将其余字段传递到不同的API,因为主要功能,例如搜索自动填充可能依赖于他们。同样,它也可以被父api 轻松引用。

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

这样还可以更快地进行搜索索引良好的压缩。而不是遍历整个单个JSON api 要搜索相关的标记动态结构有助于减少开销

这种方法还有许多其他主要用途,但我只提到了其中一些我认为可以帮助你以这种方式设计的方法。

答案 6 :(得分:1)

B:使用独立的json节点

更容易

正如其他答案所说,我会选择B作为封装原因,但我会给出另一个实用的理由:想想你自己开发的通用流程是什么,或者你是否使用库:我将使用&# 34;杰克逊&#34; (似乎可以在Android上使用它。)

  • 如果类型在您解析的JsonNode之外,则必须在反序列化器中为类型所在的每个属性指定,如果它在同一个Node内,则只指定类型所在的位置&#34;在对象&#34;,对于许多对象都可以是相同的。
  • 另外一个论点,如果你通过&#34; Battle&#34;只有对象,它没有容器,所以没有外部属性来指定类型
  • 其他论点,至少有一个JS库使用这种技术:ExtJS,参见&#34; xtype&#34;文档http://docs.sencha.com/extjs/5.0.1/guides/getting_started/getting_started.html
  • 中的属性

所以这是你要用好的类型解析的节点:

{
   "type": "BATTLE",
   "aPropertyOfBattle":1

 }

这是

的杰克逊代码
@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...
}

杰克逊为&#34; gessing&#34;提供解决方案。类型:

完整的工作代码:

@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解析节点。

编辑:我发现自从我提供技术解决方案后,它看起来似乎不合适了,所以我确切地说这里的论点不是使用杰克逊,而是表明无论你选择哪种解决方案语言和库,它都是可以使用&#34; B&#34;优雅的解决方案。

D:封装节点 另一个解决方案就是这个:

 {
   "BATTLE":{
       "aPropertyOfBattle":1
   }
 }

解析可能更容易:获取属性名称,然后使用任何工具(Gson或其他......)解析子节点 在杰克逊,唯一的区别是你使用include = As.WRAPPER_OBJECT

不方便的是,在Javascript中使用它的逻辑性较差,因为在结构中间有一个无用的节点。

杰克逊的其他解决方案

此库作为include = As....

背后的其他选项
  • As.WRAPPER_ARRAY
  • As.EXTERNAL_PROPERTY

    WRAPPER_ARRAY也更容易解析,但我发现它不优雅(完全是主观的):

    [
      "BATTLE",
      { 
        "aPropertyOfBattle":1
      }
    ]
    

    EXTERNAL_PROPERTY将是 A。解决方案,但正如我所说,您必须在每次使用变量时指定它而不是在类型类中(它对我来说缺乏连贯性) ,因为您可以在不同的上下文中使用&#34; Battle&#34;对象,具有不同的类型命名约定)

    @JsonTypeInfo (use = JsonTypeInfo.Id.NAME, include = As.EXTERNAL_PROPERTY, property = "template_type")
    private ICustomType template;
    

再次注意,我受到了杰克逊功能的启发,但它可以应用于每种语言的每个解决方案。