1

Here's a simple example: an API returns a list of possible options with the following structure:

{
  "familystatus": [
    {
      "Id": 1,
      "Name": "single"
    },
    {
      "Id": 2,
      "Name": "married"
    }
  ],
  "gender": [
    {
      "Id": 1,
      "Name": "male"
    },
    {
      "Id": 2,
      "Name": "female"
    }
  ]
}

Which translates to Typescript (as example) into something like:

export interface Table {
  familyStatus: LookupSelection[];
  gender: LookupSelection[];
}

export interface LookupSelection {
  id: number;
  name: string;
}

When I want to display those options in a list, the separation of concern principle would force me to define a different type:

export interface SelectboxOption {
  id: any;
  name: string;
}

So it happens to be that both server response and the client code overlap (so I can directly bind the server result into my client model), but it could as well be different (i.e. SelectboxOption had label and code instead of id and name.)

  • Should I forcibly design my client-oriented model to overlap the server model? (Less code and adds "transparency" - but unclear and breaks common principles)
  • Should I use in-class converters (make SelectboxOption a class with a constructor, for example)? (Fail-safe but not as clean as other options)
  • Should I make a converter class? (Most explicit but feels redundant)

Either way I go, it feels like breaking a law.

Thanks!

2
  • Wrap the type in an adapter which expose the required interface rather than convert or copy the data.
    – JacquesB
    Jul 21, 2021 at 15:39
  • 1
    Should I forcibly design my client-oriented model to overlap the server model? curiously, it's the other way around. You model the server response the way that best works for the client-side. It means that the type conversion happens on the server side. The controller transforms the domain|business model into a model that clients will appreciate most. As @ThomasJunk answered, it's not best practice, it's just consequential
    – Laiv
    Jul 24, 2021 at 11:47

2 Answers 2

4

From a pragmatic point of view:

What is it that you want to accomplish? If you group data into types, you want to achieve that it acts according to a certain contract:

Looking at your example, the data of familystatus and gender, you have two fields id and name and their respective types int and string.

So from what was said above, it would totally make sense to identify a common type, e.g. NumericOption.

Its name leads the reader to the usecase : it is an option type where the key is a number

In a possible application there could be a StringOption which is different in name and purpose. Given one or the other you get what you expected. But you could not take one for the other.

Looking at your code

export interface SelectboxOption {
  id: any;
  name: string;
}

any is the signal word here. By having this, you make a contract: I am a Key-Value structure, where the only thing which is type-enforced is, having a name being a string.

So, if you need to present your data in a list abstracting from the type of its key, a general Type would be ok.

So it happens to be that both server response and the client code overlap (so I can directly bind the server result into my client model), but it could as well be different (i.e. SelectboxOption had label and code instead of id and name.)Problems arise, if you need the more specific type - say to fill a form.

What do you mean here? Is it unclear, what exactly comes from the server?

Then you would have to make a more abstract type like an array of key-value-pairs or the like.

But chances are good that when there are type definitions on the server side, even implicite ones where the type could be inferred, that you could replicate them on the client side with Typescript.


tl;dr

Types help to group things under a contract. Every object of the same kind has the same structure / contract. Using any softens the contract.

Is it a good/bad practice to create overlapping types?

Using types gives some kind of security regarding your data. And the use of a typed language on the server-side as well as on the client-side would give you the same kind of security. So having corresponding types would be consequential.

I hope this helps

1

You should type the response from the server and your client code separately. TypeScript is very flexible with its structural typing, so I would keep the names of the fields in your client code the same if possible so you don't have to do any conversions. For example:

export interface Table {
  familyStatus: FamilyStatusRecord[];
  gender: GenderRecord[];
}

export interface FamilyStatusRecord {
    id: number,
    name: "single" | "married"
}
export interface GenderRecord{
    id: number,
    name: "male" | "female" | "other"
}

export interface SelectboxOption {
  id: number;
  name: string;
}

const tbl: Table = {
  "familyStatus": [
      {"id": 1,"name": "single"},
      {"id": 2,"name": "married"}
  ],
  "gender": [
    {"id": 1,"name": "male"},
    {"id": 2,"name": "female"}
  ]
};
function showOptions(options: SelectboxOption[]) {
    //todo: show options
}

showOptions(tbl.familyStatus);
showOptions(tbl.gender);

Notice how the showOptions function will accept an FamilyStatusRecord[] or a GenderRecord[] even though it declared options to be a SelectBoxOption[]. Now if you do need to change the SelectboxOption interface. You will get a compile error, and you will know exactly where to fix it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.