diff --git a/codegen-java/src/lib.rs b/codegen-java/src/lib.rs index 2871d41..e35dd35 100644 --- a/codegen-java/src/lib.rs +++ b/codegen-java/src/lib.rs @@ -12,11 +12,16 @@ pub fn codegen(json: serde_json::Value, out: &mut dyn io::Write) -> io::Result<( } struct Java { - root: String, + root: RootType, classes: Vec, unions: Vec, } +enum RootType { + Extension(String), // extends ... + Wrapper(String), // wrapper around ... +} + struct Class { name: String, vars: Vec, @@ -53,10 +58,36 @@ impl From for Java { }, ); - let mut root = String::from("Object"); + let mut root = RootType::Extension("Object".into()); let mut classes = vec![]; let mut unions = vec![]; + // Determine root type + if let Some(type_def) = type_graph.nodes.get(&type_graph.root) { + match type_def { + TypeDef::Object(_) => { + root = RootType::Extension(derive_type_name( + type_graph.root, + &type_graph, + &name_registry, + )) + } + TypeDef::Array(inner_type_id) => { + root = RootType::Extension(format!( + "java.util.ArrayList<{}>", + derive_type_name(*inner_type_id, &type_graph, &name_registry) + )) + } + _ => { + root = RootType::Wrapper(derive_type_name( + type_graph.root, + &type_graph, + &name_registry, + )) + } + }; + } + // TODO: instead of iterating through type_graph.nodes // and processing TypeDef::Object and TypeDef::Union, // do a bfs traversal (starting from root type_id) @@ -68,21 +99,6 @@ impl From for Java { // TODO: to avoid case-insensitive name clash with ROOT, // try to inline the root type in the top level JsonCodeGen for (type_id, type_def) in &type_graph.nodes { - if *type_id == type_graph.root { - match type_def { - TypeDef::Object(_) => { - root = derive_type_name(*type_id, &type_graph, &name_registry) - } - TypeDef::Array(inner_type_id) => { - root = format!( - "java.util.ArrayList<{}>", - derive_type_name(*inner_type_id, &type_graph, &name_registry) - ) - } - _ => { /* no-op */ } - }; - } - if let TypeDef::Object(object_fields) = type_def { let class_name = name_registry .assigned_name(*type_id) @@ -298,6 +314,7 @@ fn write(java: Java, out: &mut dyn io::Write) -> io::Result<()> { .iter() .flat_map(|c| &c.vars) .any(|v| v.annotate) + || matches!(java.root, RootType::Wrapper(_)) { writeln!(out, "import com.fasterxml.jackson.annotation.*;")?; } @@ -319,7 +336,20 @@ fn write(java: Java, out: &mut dyn io::Write) -> io::Result<()> { // class with name ROOT (SCREAMING_SNAKE_CASE) // will never clash with other classes (PascalCase) writeln!(out, "\t// entry point = ROOT")?; - writeln!(out, "\tpublic static class ROOT extends {} {{}}", java.root)?; + match java.root { + RootType::Extension(base) => { + writeln!(out, "\tpublic static class ROOT extends {} {{}}", base)?; + } + RootType::Wrapper(inner) => { + writeln!(out, "\tpublic static class ROOT {{")?; + writeln!(out, "\t\tprivate final {} value;", inner)?; + writeln!(out, "\t\t@JsonCreator(mode = JsonCreator.Mode.DELEGATING)")?; + writeln!(out, "\t\tpublic ROOT({} value) {{ this.value = value; }}", inner)?; + writeln!(out, "\t\t@JsonValue")?; + writeln!(out, "\t\tpublic {} getValue() {{ return value; }}", inner)?; + writeln!(out, "\t}}")?; + } + } for class in java.classes { if class.needs_custom_serializer_deserializer { diff --git a/codegen-rust/src/lib.rs b/codegen-rust/src/lib.rs index 241251d..b7fa9e1 100644 --- a/codegen-rust/src/lib.rs +++ b/codegen-rust/src/lib.rs @@ -53,34 +53,42 @@ impl From for Rust { let mut structs = vec![]; let mut enums = vec![]; - for (type_id, type_def) in &type_graph.nodes { - if *type_id == type_graph.root { - match type_def { - TypeDef::Object(_) => { - root = derive_type_name( - *type_id, + if let Some(type_def) = type_graph.nodes.get(&type_graph.root) { + match type_def { + TypeDef::Object(_) => { + root = derive_type_name( + type_graph.root, + &type_graph, + &name_registry, + type_graph.root, + &back_edges, + ) + } + TypeDef::Array(inner_type_id) => { + root = format!( + "Vec<{}>", + derive_type_name( + *inner_type_id, &type_graph, &name_registry, - *type_id, - &back_edges, + type_graph.root, + &back_edges ) - } - TypeDef::Array(inner_type_id) => { - root = format!( - "Vec<{}>", - derive_type_name( - *inner_type_id, - &type_graph, - &name_registry, - *type_id, - &back_edges - ) - ) - } - _ => { /* no-op */ } - }; - } + ) + } + _ => { + root = derive_type_name( + type_graph.root, + &type_graph, + &name_registry, + type_graph.root, + &back_edges, + ) + } + }; + } + for (type_id, type_def) in &type_graph.nodes { if let TypeDef::Object(object_fields) = type_def { let struct_name = name_registry .assigned_name(*type_id) diff --git a/core/src/schema.rs b/core/src/schema.rs index 17455ce..81cf70d 100644 --- a/core/src/schema.rs +++ b/core/src/schema.rs @@ -8,9 +8,7 @@ //! //! ### [`Schema`] //! -//! The top-level schema representation can be either: -//! - **Object**: A JSON object with named fields `{name:str,age:int}` -//! - **Array**: A JSON array with homogeneous or heterogeneous elements `[float]` +//! The top-level schema representation wrapping a single [`FieldType`]. //! //! ### [`Field`] //! @@ -79,13 +77,10 @@ use serde_json::{Map, Value}; use std::fmt::Display; -/// Top-level schema: either an Object with fields or an Array with element type. -/// -/// Fields are sorted alphabetically for canonical representation. +/// Top-level schema wrapping the inferred type. #[derive(Debug, Clone, PartialEq)] -pub enum Schema { - Object(Vec), - Array(FieldType), +pub struct Schema { + pub ty: FieldType, } /// A named field within an object type. @@ -114,19 +109,11 @@ pub enum FieldType { impl From for Schema { fn from(json: Value) -> Self { - let mut schema = match json { - Value::Array(arr) => Self::Array(array(arr)), - Value::Object(obj) => Self::Object(object(obj)), - _ => unreachable!("Valid top level Value will always be object or array"), - }; - - // sort schema to make sure it has a deterministic order - match &mut schema { - Schema::Object(fields) => sort_fields(fields), - Schema::Array(field_type) => sort_field_type(field_type), - } + let mut field_type = field_type(json); + // recursively sort field_type to make sure it has a deterministic order + sort_field_type(&mut field_type); - schema + Schema { ty: field_type } } } @@ -525,10 +512,7 @@ fn field_type(value: Value) -> FieldType { impl Display for Schema { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Schema::Object(fields) => write!(f, "{{{}}}", FieldsDisp(fields)), - Schema::Array(field_type) => write!(f, "[{}]", field_type), - } + write!(f, "{}", self.ty) } } diff --git a/core/src/type_graph.rs b/core/src/type_graph.rs index 2f7ac78..66ef070 100644 --- a/core/src/type_graph.rs +++ b/core/src/type_graph.rs @@ -265,13 +265,7 @@ impl GraphBuilder { fn build(schema: Schema) -> TypeGraph { let mut builder = GraphBuilder::default(); - let root_type_id = match schema { - Schema::Object(fields) => builder.process_fields(fields), - Schema::Array(field_type) => { - let inner_type_id = builder.process_field_type(field_type); - builder.intern(TypeDef::Array(inner_type_id)) - } - }; + let root_type_id = builder.process_field_type(schema.ty); TypeGraph { root: root_type_id, diff --git a/test-data/top-level-bool.json b/test-data/top-level-bool.json new file mode 100644 index 0000000..f32a580 --- /dev/null +++ b/test-data/top-level-bool.json @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/test-data/top-level-float.json b/test-data/top-level-float.json new file mode 100644 index 0000000..2e0c11f --- /dev/null +++ b/test-data/top-level-float.json @@ -0,0 +1 @@ +12.34 \ No newline at end of file diff --git a/test-data/top-level-int.json b/test-data/top-level-int.json new file mode 100644 index 0000000..bd41cba --- /dev/null +++ b/test-data/top-level-int.json @@ -0,0 +1 @@ +12345 \ No newline at end of file diff --git a/test-data/top-level-null.json b/test-data/top-level-null.json new file mode 100644 index 0000000..ec747fa --- /dev/null +++ b/test-data/top-level-null.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/test-data/top-level-string.json b/test-data/top-level-string.json new file mode 100644 index 0000000..cfcb15c --- /dev/null +++ b/test-data/top-level-string.json @@ -0,0 +1 @@ +"hello world" \ No newline at end of file