Validating JSON in Flutter

Ayush P Gupta
CodeChai
Published in
5 min readSep 13, 2018

One of the most beloved package that i like in flutter is the json_annotation. JSON parsing has never been so fun for me as that in dart. The reason might be my ignorance with JSON or may be this package is really better than others.

Basic JSON parsing is well explained here at official site. And because it was already clearly explained, hence i will not dive into the basics of parsing. Also i will assume that the reader has at least tried simple JSON parsing in Flutter.

So before starting let’s make a simple json_parsing project in flutter.

Here’s the sample JSON that we’ll be using thereafter:

{
“id”: “12”,
“name”: “Ayush”,
“nickname”: “apgapg”,
“avatar”: “https://lh5.googleusercontent.com/-Oeijd1cy4wc/AAAAAAAAAAI/AAAAAAAAf6o/e3VRuG09d48/s96-c/photo.jpg",
“profile_complete”: false
}

We’ll be needing a model class for parsing it:

import 'package:json_annotation/json_annotation.dart';

part 'sample_model.g.dart';

@JsonSerializable()
class SampleModel {
final String id;
final String name;
final String nickname;
final String avatar;
final bool profile_complete;

SampleModel(this.id, this.name, this.nickname, this.avatar, this.profile_complete);

factory SampleModel.fromJson(Map<String, dynamic> json) => _$SampleModelFromJson(json);
}

Finally here’s the homepage where the main implementation takes place: (only State is shown)

class _MyHomePageState extends State<MyHomePage> {
SampleModel sampleModel;
final textStyle = TextStyle(fontSize: 18.0);

@override
void initState() {
super.initState();
//Parsing the json to object sampleModel = new SampleModel.fromJson(json.decode(Strings.SAMPLE_JSON));
assert(sampleModel != null);
}

@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("id: " + sampleModel.id, style: textStyle),
Text("name: " + sampleModel.name, style: textStyle),
Text("nickname: " + sampleModel.nickname, style: textStyle),
Text("avatar: " + sampleModel.nickname, style: textStyle),
Text("profileComplete: " + (sampleModel.profile_complete ? "Yes" : "No"), style: textStyle),
],
)),
);
}
}

The output of the above code looks something like this:

Pretty simple. We just parsed a basic JSON object.

The above code will work fine till the time server returns JSON correctly. The problem starts, when JSON becomes quite large and it becomes difficult to debug when server returns JSON incorrectly. Let’s see how:

Case 1:

For example, say, server didn’t return ‘profile_complete’ key i.e. your JSON looks something like this:

{
“id”: “12”,
“name”: “Ayush”,
“nickname”: “apgapg”,
“avatar”: “https://lh5.googleusercontent.com/-Oeijd1cy4wc/AAAAAAAAAAI/AAAAAAAAf6o/e3VRuG09d48/s96-c/photo.jpg",
// profile_complete key missing
}

The code will give error on run time because you are using the ‘profile_complete’ value while setting a text in homePage:

Text("profileComplete: " + (sampleModel.profile_complete ? "Yes" : "No"), style: textStyle)     //Throwing null pointer error

You might say “i can put a null check there” but it doesn’t make sense to put null check everywhere in your code. So why not validate JSON while parsing only?

Thanks to json_annotation package you can automate the whole process.

We will now change our ‘ profile_complete’ key to look something like below. We will add an annotation ‘@JsonKey’ with some parameters like this:

@JsonKey(required: true)
final bool profile_complete;

The docs about this ‘required’ method says:

{bool required} Static type: bool 
When true, generated code for fromJson will verify that the source JSON map contains the associated key.
If the key does not exist, a MissingRequiredKeysException exception is thrown.
Note: only the existence of the key is checked. A key with a null value is considered valid.

The docs clearly states what will happen under the hood and I guess no further explanation is needed.

Case 2:

What if server now returns ‘profile_complete’ key but with a null value. Something like this:

{
“id”: “12”,
“name”: “Ayush”,
“nickname”: “apgapg”,
“avatar”: “https://lh5.googleusercontent.com/-Oeijd1cy4wc/AAAAAAAAAAI/AAAAAAAAf6o/e3VRuG09d48/s96-c/photo.jpg",
“profile_complete”: null
}

Again this will be an issue because you need a bool rather than a null value. Here’s how we can put a check again:

@JsonKey(disallowNullValue: true)
final bool profile_complete;

Docs about ‘disallowNullValue’ says the following:

{bool disallowNullValue} Static type: bool 
If true, generated code will throw a DisallowedNullValueException if the corresponding key exits, but the value is null.
Note: this value does not affect the behavior of a JSON map without the associated key.
If [disallowNullValue] is true, [includeIfNull] will be treated as false to ensure compatibility between toJson and fromJson.
If both [includeIfNull] and [disallowNullValue] are set to true on the same field, an exception will be thrown during code generation.

Case 3:

What if a case occurs where you want to assign default value to a key if it doesn’t exist in source JSON or its value is null. For example, Let’s say we want to assign default value to ‘profile_complete’ key if Case 1 or Case 2 occurs as above:

@JsonKey(defaultValue: false)
final bool profile_complete;

This will now assign a default value to this key if its absent or its value is null.

For ‘defaultValue’ docs says that:

{Object defaultValue} Static type: Object 
The value to use if the source JSON does not contain this key or if the value is null.

Case 4:

One more case can occur, which is when the server returns ‘profile_complete’ key with value not bool but string :

{
“id”: “12”,
“name”: “Ayush”,
“nickname”: “apgapg”,
“avatar”: “https://lh5.googleusercontent.com/-Oeijd1cy4wc/AAAAAAAAAAI/AAAAAAAAf6o/e3VRuG09d48/s96-c/photo.jpg",
“profile_complete”: "false" //Note the data type its string now
}

Don’t say the server needs to send proper data type or correct JSON. There can be cases where server may not be able to return the exact data type that is required at the client end and you are required to use the string(in this case) value only.

Don’t worry, even this can also be done i.e. the source data type can also be converted into different data type. You need ‘fromJson’ method:

@JsonKey(fromJson: _convertTobool)
final bool profile_complete;

‘fromJson’ uses a method ‘_convertTobool’ which is defined somewhere like this:

bool _convertTobool(value) {
if (value is String) {
if (value.toLowerCase() == "true")
return true;
else
return false
;
} else
return
value;
}

This method basically checks the data type of the value received in key’s value and converts it to the proper data type when required.

Case 5:

Last but not the least, what if you want to use different field name to the one received in JSON. For example, we want to use ‘profile_complete’ key as ‘profileComplete’ in our code?

Here’s the solution. Use the ‘name’ method in annotation like this:

@JsonKey(name:"profile_complete")
final bool profileComplete;

Don’t forget to re-run the build runner every time you make a change i.e. run this command whenever changing the model:

flutter packages pub run build_runner build

That’s all for validating JSON returned from server. The complete sample code is available here.

Whola! Both you and I learnt something new today. Congrats
Clap! Clap! Clap!

Further Reading:

  1. https://flutter.io/json/
  2. https://pub.dartlang.org/packages/json_serializable
  3. https://github.com/dart-lang/json_serializable/blob/master/example/lib/example.dart

The Flutter Pub is a medium publication to bring you the latest and amazing resources such as articles, videos, codes, podcasts etc. about this great technology to teach you how to build beautiful apps with it. You can find us on Facebook, Twitter, and Medium or learn more about us here. We’d love to connect! And if you are a writer interested in writing for us, then you can do so through these guidelines.

--

--

Ayush P Gupta
CodeChai

NodeJs | VueJs | Kubernetes | Flutter | Linux | DIY person