summaryrefslogtreecommitdiff
path: root/libjsonpb/README.md
blob: 46bd84c8b853b67a9fd0d3c0f6f3165442e20d0b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# `libjsonpbparse`

This library provides functions to parse a JSON file to a structured Protobuf
message.

At this time of writing, `libprotobuf-cpp-full` is at version 3.0.0-beta, and
unknown fields in a JSON file cannot be ignored. Do **NOT** use this library in
vendor / recovery until `libprotobuf-cpp-full` is updated.

## Using `libjsoncpp` in parser code

Since `libjsonpbparse` cannot be used in vendor / recovery processes yet,
`libjsoncpp` is used instead. However, there are notable differences in the
logic of `libjsoncpp` and `libprotobuf` when parsing JSON files.

- There are no implicit string to integer conversion in `libjsoncpp`. Hence:
  - If the Protobuf schema uses 64-bit integers (`(s|fixed|u|)int64`):
    - The JSON file must use strings (to pass tests in `libjsonpbverify`)
    - Parser code (that uses `libjsoncpp`) must explicitly convert strings to
      integers. Example:
      ```c++
      strtoull(value.asString(), 0, 10)
      ```
  - If the Protobuf schema uses special floating point values:
    - The JSON file must use strings (e.g. `"NaN"`, `"Infinity"`, `"-Infinity"`)
    - Parser code must explicitly handle these cases. Example:
      ```c++
      double d;
      if (value.isNumeric()) {
        d = value.asDouble();
      } else {
        auto&& s = value.asString();
        if (s == "NaN") d = std::numeric_limits<double>::quiet_NaN();
        else if (s == "Infinity") d = std::numeric_limits<double>::infinity();
        else if (s == "-Infinity") d = -std::numeric_limits<double>::infinity();
      }
      ```
- `libprotobuf` accepts either `lowerCamelCase` (or `json_name` option if it is
  defined) or the original field name as keys in the input JSON file.
  The test in `libjsonpbverify` explicitly check this case to avoid ambiguity;
  only the original field name (or `json_name` option if it is defined) can be
  used.

Once `libprotobuf` in the source tree is updated to a higher version and
`libjsonpbparse` is updated to ignore unknown fields in JSON files, all parsing
code must be converted to use `libjsonpbparse` for consistency.

# `libjsonpbverify`

This library provides functions and tests to examine a JSON file and validate
it against a Protobuf message definition.

In addition to a validity check that `libprotobuf` can convert the JSON file to a
Protobuf message (using `libjsonpbparse`), it also checks the following:

- Whether there are fields unknown to the schema. All fields in the JSON file
  must be well defined in the schema.
- Whether the Protobuf file defines JSON keys clearly. The JSON keys must be
  the `json_name` option of a Protobuf field, or name of a Protobuf field if
  `json_name` is not defined. `lowerCamelCase` supported by `libprotobuf` is
  explicitly disallowed (unless explicitly used in `json_name`). For example,
  in the following Protobuf file, only keys `foo_bar` and `barBaz` are allowed
  in the JSON file:
  ```
  message Foo {
      string foo_bar = 1;
      string bar_baz = 2 [json_name = "barBaz"];
  }
  ```
- Whether `json == convert_to_json(convert_to_pb(json))`, using `libprotobuf`.
  This imposes additional restrictions including:
  - Enum values must be present as names (not integer values) in the JSON file.
  - 64-bit integers and special floating point values (infinity, NaN) must
    always be strings.

## Defining a JSON schema using Protobuf

Check [JSON Mapping](https://developers.google.com/protocol-buffers/docs/proto3#json)
before defining a Protobuf object as a JSON schema. In general:

- **Use proto3**. `libjsonverify` does not support proto2.
- JSON booleans should be `bool`.
- JSON numbers should be `(s|fixed|u|)int32`, `float`, or `double` in the schema
- JSON strings are generally `string`s, but if you want to impose more
  restrictions on the string, you can also use `Timestamp`, `bytes`,
  **`float`** or **`double`** (if NaN and infinity are valid values),
  enumerations, etc.
  - If a custom enumeration is used, parser code should **NOT** error when the
    enumeration value name is unknown, as enumeration definitions may be
    extended in the future.
- JSON arrays should be repeated fields.
- JSON objects should be a well-defined `message`, unless you have a good reason
  to use `map<string, T>`.
- Don't use `Any`; it defeats the purpose of having the schema.

## Validating a JSON file against a Protobuf definition

Example:
```c++
#include <jsonpb/verify.h>
using namespace ::android::jsonpb;
std::unique_ptr<JsonSchemaTestConfig> CreateCgroupsParam() {

}
INSTANTIATE_TEST_SUITE_P(LibProcessgroupProto, JsonSchemaTest,
                         ::testing::Values(MakeTestParam<Cgroups>("cgroups.json")));
```