Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 48 additions & 18 deletions codegen-java/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Class>,
unions: Vec<Union>,
}

enum RootType {
Extension(String), // extends ...
Wrapper(String), // wrapper around ...
}

struct Class {
name: String,
vars: Vec<MemberVar>,
Expand Down Expand Up @@ -53,10 +58,36 @@ impl From<serde_json::Value> 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)
Expand All @@ -68,21 +99,6 @@ impl From<serde_json::Value> 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)
Expand Down Expand Up @@ -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.*;")?;
}
Expand All @@ -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 {
Expand Down
56 changes: 32 additions & 24 deletions codegen-rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,34 +53,42 @@ impl From<serde_json::Value> 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)
Expand Down
34 changes: 9 additions & 25 deletions core/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`]
//!
Expand Down Expand Up @@ -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<Field>),
Array(FieldType),
pub struct Schema {
pub ty: FieldType,
}

/// A named field within an object type.
Expand Down Expand Up @@ -114,19 +109,11 @@ pub enum FieldType {

impl From<Value> 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 }
}
}

Expand Down Expand Up @@ -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)
}
}

Expand Down
8 changes: 1 addition & 7 deletions core/src/type_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions test-data/top-level-bool.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
true
1 change: 1 addition & 0 deletions test-data/top-level-float.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
12.34
1 change: 1 addition & 0 deletions test-data/top-level-int.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
12345
1 change: 1 addition & 0 deletions test-data/top-level-null.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
null
1 change: 1 addition & 0 deletions test-data/top-level-string.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"hello world"
Loading