json_schema_generator/
lib.rs1use serde_json::{json, Map, Value};
2
3pub fn generate_json_schema(instance: &Value) -> Value {
4 match instance {
5 Value::Object(_) => generate_object_schema(instance),
6 Value::Array(arr) => generate_array_schema(arr),
7 Value::String(_) => json!({"type": "string"}),
8 Value::Number(n) => {
9 if n.is_i64() {
10 json!({"type": "integer"})
11 } else {
12 json!({"type": "number"})
13 }
14 }
15 Value::Bool(_) => json!({"type": "boolean"}),
16 Value::Null => json!({"type": "null"}),
17 }
18}
19
20fn generate_object_schema(instance: &Value) -> Value {
21 let mut schema = json!({
22 "type": "object",
23 "properties": {},
24 "required": []
25 });
26
27 if let Value::Object(obj) = instance {
28 for (key, value) in obj {
29 if key == "$ref" {
30 schema["$ref"] = value.clone();
31 } else {
32 let mut sub_schema = generate_json_schema(value);
33 if let Some(obj) = sub_schema.as_object_mut() {
34 obj.remove("$schema"); }
36 schema["properties"][key] = sub_schema;
37 schema["required"]
38 .as_array_mut()
39 .unwrap()
40 .push(Value::String(key.clone()));
41 }
42 }
43 }
44
45 if let Some(required) = schema["required"].as_array_mut() {
47 required.sort_by(|a, b| a.as_str().unwrap().cmp(b.as_str().unwrap()));
48 }
49
50 schema["$schema"] = json!("http://json-schema.org/draft-07/schema#");
52
53 schema
54}
55
56fn generate_array_schema(arr: &Vec<Value>) -> Value {
57 if arr.is_empty() {
58 return json!({
59 "type": "array",
60 "items": {}
61 });
62 }
63
64 let item_schemas: Vec<Value> = arr.iter().map(generate_json_schema).collect();
65 let common_schema = find_common_schema(&item_schemas);
66
67 json!({
68 "type": "array",
69 "items": common_schema
70 })
71}
72
73fn find_common_schema(schemas: &[Value]) -> Value {
74 if schemas.is_empty() {
75 return json!({});
76 }
77
78 let mut common = schemas[0].clone();
79 for schema in schemas.iter().skip(1) {
80 common = merge_schemas(&common, schema);
81 }
82
83 common
84}
85
86fn merge_schemas(schema1: &Value, schema2: &Value) -> Value {
87 if schema1 == schema2 {
88 return schema1.clone();
89 }
90
91 let mut merged = json!({
92 "oneOf": [schema1, schema2]
93 });
94
95 if let (Value::Object(obj1), Value::Object(obj2)) = (schema1, schema2) {
96 if obj1.get("type") == obj2.get("type") {
97 merged = json!({
98 "type": obj1["type"].clone()
99 });
100
101 if obj1.contains_key("properties") && obj2.contains_key("properties") {
102 let mut properties = Map::new();
103 let props1 = obj1["properties"].as_object().unwrap();
104 let props2 = obj2["properties"].as_object().unwrap();
105
106 for (key, value) in props1.iter().chain(props2.iter()) {
107 properties.insert(key.clone(), value.clone());
108 }
109
110 merged["properties"] = Value::Object(properties);
111 }
112 }
113 }
114
115 merged
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use serde_json::json;
122
123 #[test]
124 fn test_generate_json_schema_string() {
125 let input = json!("test");
126 let expected = json!({"type": "string"});
127 assert_eq!(generate_json_schema(&input), expected);
128 }
129
130 #[test]
131 fn test_generate_json_schema_integer() {
132 let input = json!(42);
133 let expected = json!({"type": "integer"});
134 assert_eq!(generate_json_schema(&input), expected);
135 }
136
137 #[test]
138 fn test_generate_json_schema_number() {
139 let input = json!(3.14);
140 let expected = json!({"type": "number"});
141 assert_eq!(generate_json_schema(&input), expected);
142 }
143
144 #[test]
145 fn test_generate_json_schema_boolean() {
146 let input = json!(true);
147 let expected = json!({"type": "boolean"});
148 assert_eq!(generate_json_schema(&input), expected);
149 }
150
151 #[test]
152 fn test_generate_json_schema_null() {
153 let input = json!(null);
154 let expected = json!({"type": "null"});
155 assert_eq!(generate_json_schema(&input), expected);
156 }
157
158 #[test]
159 fn test_generate_object_schema() {
160 let input = json!({
161 "name": "John Doe",
162 "age": 30,
163 "is_student": false
164 });
165 let expected = json!({
166 "$schema": "http://json-schema.org/draft-07/schema#",
167 "type": "object",
168 "properties": {
169 "name": {"type": "string"},
170 "age": {"type": "integer"},
171 "is_student": {"type": "boolean"}
172 },
173 "required": ["age", "is_student", "name"]
174 });
175 assert_eq!(generate_json_schema(&input), expected);
176 }
177
178 #[test]
179 fn test_generate_array_schema() {
180 let input = json!([1, 2, 3]);
181 let expected = json!({
182 "type": "array",
183 "items": {"type": "integer"}
184 });
185 assert_eq!(generate_json_schema(&input), expected);
186 }
187
188 #[test]
189 fn test_generate_array_schema_mixed_types() {
190 let input = json!([1, "two", 3.0]);
191 let expected = json!({
192 "type": "array",
193 "items": {
194 "oneOf": [
195 {"oneOf": [
196 {"type": "integer"},
197 {"type": "string"}
198 ]},
199 {"type": "number"}
200 ]
201 }
202 });
203 assert_eq!(generate_json_schema(&input), expected);
204 }
205
206 #[test]
207 fn test_generate_array_schema_empty() {
208 let input = json!([]);
209 let expected = json!({
210 "type": "array",
211 "items": {}
212 });
213 assert_eq!(generate_json_schema(&input), expected);
214 }
215
216 #[test]
217 fn test_find_common_schema() {
218 let schemas = vec![
219 json!({"type": "integer"}),
220 json!({"type": "string"}),
221 json!({"type": "boolean"}),
222 ];
223 let expected = json!({
224 "oneOf": [
225 {"oneOf": [
226 {"type": "integer"},
227 {"type": "string"}
228 ]},
229 {"type": "boolean"}
230 ]
231 });
232 assert_eq!(find_common_schema(&schemas), expected);
233 }
234
235 #[test]
236 fn test_merge_schemas_same_type() {
237 let schema1 = json!({"type": "object", "properties": {"a": {"type": "string"}}});
238 let schema2 = json!({"type": "object", "properties": {"b": {"type": "integer"}}});
239 let expected = json!({
240 "type": "object",
241 "properties": {
242 "a": {"type": "string"},
243 "b": {"type": "integer"}
244 }
245 });
246 assert_eq!(merge_schemas(&schema1, &schema2), expected);
247 }
248
249 #[test]
250 fn test_merge_schemas_different_types() {
251 let schema1 = json!({"type": "string"});
252 let schema2 = json!({"type": "integer"});
253 let expected = json!({
254 "oneOf": [
255 {"type": "string"},
256 {"type": "integer"}
257 ]
258 });
259 assert_eq!(merge_schemas(&schema1, &schema2), expected);
260 }
261
262 #[test]
263 fn test_generate_schema_with_ref() {
264 let input = json!({
265 "$ref": "#/definitions/address",
266 "address": {
267 "street": "123 Main St",
268 "city": "New York"
269 }
270 });
271 let expected = json!({
272 "$schema": "http://json-schema.org/draft-07/schema#",
273 "type": "object",
274 "properties": {
275 "address": {
276 "type": "object",
277 "properties": {
278 "street": {"type": "string"},
279 "city": {"type": "string"}
280 },
281 "required": ["city", "street"]
282 }
283 },
284 "required": ["address"],
285 "$ref": "#/definitions/address"
286 });
287 assert_eq!(generate_json_schema(&input), expected);
288 }
289}