Cœur
Cœur

Reputation: 38667

JSON: better define a type of object inside or outside the object?

Context

We are building a JSON API for web (HTML+JS) and mobile (iOS/Android/Windows).

Server needs to send data with a base structure and a variable structure. In our example, the base structure includes "name" and "description", the variable structure is called "template" and have different fields depending on its type. We figured out at least three ways to write it (there may be more):

A: variable structure type defined outside the object

{
  "id": "A001",
  "name": "My First Game",
  ...,
  "template_type": "BATTLE",
  "template": {
    ...
  }
}

In this scenario, the client should look at "template_type" in order to determine how to parse "template". The "template" object alone is not self-sufficient to know what it is.

B: variable structure type defined inside the object

{
  "id": "A001",
  "name": "My First Game",
  ...,
  "template": {
    "type": "BATTLE",
    ...
  }
}

In this scenario, the client should look at "type" inside "template" in order to determine how to parse "template". The "template" object alone is self-sufficient to know what it is.

C: variable structure type defined by the key of the object

{
  "id": "A001",
  "name": "My First Game",
  ...,
  "template_battle": {
    ...
  }
}

In this scenario, the client should look at all keys ("template_battle", "template_puzzle", ...) in order to determine which type of game we have. The "template_battle" object alone is self-sufficient to know what it is, because it would always be the "BATTLE" type.

Question

Any recommendation on which JSON solution is the most client friendly for web and mobile to parse and use? (you can propose other solutions)

Upvotes: 7

Views: 1706

Answers (7)

pdem
pdem

Reputation: 4067

B: It is easier to use a standalone json node

As other answers says, I would go for B for encapsulation reason, but I will give another pragmatic reason: Think of what would do a generic process that you develop yourself, or if you use a library: I will use "Jackson" (it seem possible to use it on Android).

  • If the type is outside the JsonNode you parse, you will have to specify in your deserializer for each property where the type is, if it is inside the same Node, you specify only where the type is "inside the object", and it can be the same for many objets.
  • An additional argument, if you pass the "Battle" object only, it doens't have a container, so no external properties to specify the type
  • Other argument, at least 1 JS library use this technique: ExtJS,see the "xtype" property in documentation http://docs.sencha.com/extjs/5.0.1/guides/getting_started/getting_started.html

So here is the Node you want to parse with the good type:

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

 }

here is the jackson code for this

@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 provide a solution for "gessing" the type:

Full working code :

@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);

}
}

Note that this is not jackson specific, you can parse the node using simple JsonNode in java.

Edit: I am seeing that it may seem out of subject since I give a technical solution, so I precise that the argument here is not to use Jackson, it is to show that whatever the solution language and library you choose, it is possible to use the "B" solution in an elegant way.

D: An encapsulating node An other solution is this one:

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

It may be easier to parse: you get the property name, then you parse the sub-node using any tool (Gson or other...) In jackson, the only difference is that you use include = As.WRAPPER_OBJECT

The inconvenient is that it is less logical to use in Javascript since you have a useless node in the middle of your structure.

Other solution of Jackson

This library as other options behind include = As....

  • As.WRAPPER_ARRAY
  • As.EXTERNAL_PROPERTY

    WRAPPER_ARRAY is easier to parse too, but i don't find it elegant (it is totally subjective) :

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

    EXTERNAL_PROPERTY would be the A. solution, but as I said, you must specify it every time you use your variable and not in your type class (it lacks of coherence to me, because you can use the "Battle" object in different context, with different type naming convention)

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

Note again, I am inspired of jackson functionality, but it can be applied for every solution in every language.

Upvotes: 2

Pritish Vaidya
Pritish Vaidya

Reputation: 22199

I would recommend you to use the static and the dynamic structure in two different collections

As shown below

Static Structure and using the dynamic field as an array and by passing the unique id or the field which you think may be unique.

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

Dynamic Structure. In the dynamic structure you can pass the rest of the fields into a different api,since the major functionality like searching,autocomplete might be dependant on them. Similarly it can also be referenced by the parent api easily.

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

This also allows faster searching,indexing and good compression.Rather than traversing through the whole single JSON api to search for the relevant tags, the dynamic structure helps in reducing the overheads.

There are many other major uses of this approach but I have mentioned only a few of them which i think would help you design in such a way.

Upvotes: 1

Alexander O'Mara
Alexander O'Mara

Reputation: 60527

I would recommend going with option A, for 2 simple reasons.

A > B because separate data and types:

It separates the type information from the data itself. By doing this, you would not have naming conflicts if say you wanted a template_type property associated with it. You could potentially simplify it enumerate all the properties and set them on your custom object, without having to have a special case to ignore the type property.

A > C because less-work:

Parsing the key string is more work. To find the template_* key in the first place, you would need to enumerate the properties and loop over them to find the one you want.

Ultimately, I think option A will give you the easiest method of parsing and using the data.

Upvotes: 4

Kiran Kumar
Kiran Kumar

Reputation: 1051

I would prefer B over the others, because it will separate the concerns/data.

Since here if you want to process only template data you can easily extract template data in one step in case of B (E.g Obj.template) But it is not easy case of A.

And also, If you add multiple types of templates in future then if you want to extract template data, it is straight forward in case B(E.g Obj.template), But in case of C , you need to write code like below,

if(template_type='temp1'){
  template=Obj["template_tep1"]
}
if(template_type='temp1'){
  template=Obj["template_tep1"]
}
if(template_type='temp1'){
  template=Obj["template_tep1"]
}

or

you need to write code like template=Obj["template"+Obj.template_type].

So I will prefer B over others.

Upvotes: 2

Bekim Bacaj
Bekim Bacaj

Reputation: 5955

I'd prefer your B variant over A and C.

However, you might also consider a structure like this:

{
   "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*/
      }
   }
};

It might give a better feel in everyday use.

Upvotes: 2

Minudika
Minudika

Reputation: 851

The approach B would be much better IMHO. That is simply because, it provides a generic approach for user to access the template's attributes without concerning about its type. In this manner, user can simply write his program for a generic template which include the type as an attribute of itself.

For example, imagine you have a object type named Template which maps the json definition of a template to a Java object.

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

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

By using approach B, you can directly map the json definition of that template, to above template object.(In this case, it is a Java object but of course the concept works for any other programming language).

User does not need to have an prior knowledge of template type, before accessing the template's definition. That's why it is said to be a more generic approach.

Upvotes: 2

samwise
samwise

Reputation: 2257

Personally, I would put the type on the template itself for a simple reason, that is encapsulation. Imagine you want to separate the creation of the template and the outside object (remember separation of concerns and the single responsibility principle (https://en.wikipedia.org/wiki/Single_responsibility_principle)). If the type is on the outside object, you will always have to specify the type of the template, to be able to create it. That's a possibility, but it increases coupling and violates encapsulation.

For further reading I recommend https://en.wikipedia.org/wiki/SOLID_(object-oriented_design) for the beginning.

Upvotes: 7

Related Questions