use crate::ts_syn::TsStream;
use crate::ts_syn::abi::DiagnosticCollector;
use crate::ts_syn::{parse_ts_expr, ts_ident};
use super::super::{SerdeContainerOptions, SerdeFieldOptions, TypeCategory};
use super::super::{get_foreign_types, rewrite_expression_namespaces};
use super::helpers::{
classify_serde_value_kind, get_serializable_type_name, nested_deserialize_fn_name,
parse_default_expr, try_composite_foreign_deserialize,
};
use super::types::{DeserializeField, ObjectVariant, SerdeValueKind, raw_cast_type};
pub(super) fn interface_field_to_deserialize_field(
field: &crate::ts_syn::abi::ir::interface::InterfaceFieldIR,
container_opts: &SerdeContainerOptions,
diagnostics: &mut DiagnosticCollector,
) -> Option<DeserializeField> {
let parse_result = SerdeFieldOptions::from_decorators(&field.decorators, &field.name);
diagnostics.extend(parse_result.diagnostics);
let opts = parse_result.options;
if !opts.should_deserialize() {
return None;
}
let json_key = opts
.rename
.clone()
.unwrap_or_else(|| container_opts.rename_all.apply(&field.name));
let type_cat = TypeCategory::from_ts_type(&field.ts_type);
let nullable_inner_kind = match &type_cat {
TypeCategory::Nullable(inner) => Some(classify_serde_value_kind(inner)),
_ => None,
};
let array_elem_kind = match &type_cat {
TypeCategory::Array(inner) => Some(classify_serde_value_kind(inner)),
_ => None,
};
let nullable_serializable_type = match &type_cat {
TypeCategory::Nullable(inner) => get_serializable_type_name(inner),
_ => None,
};
let array_elem_serializable_type = match &type_cat {
TypeCategory::Array(inner) => get_serializable_type_name(inner),
_ => None,
};
let set_elem_kind = match &type_cat {
TypeCategory::Set(inner) => Some(classify_serde_value_kind(inner)),
_ => None,
};
let set_elem_serializable_type = match &type_cat {
TypeCategory::Set(inner) => get_serializable_type_name(inner),
_ => None,
};
let map_value_kind = match &type_cat {
TypeCategory::Map(_, value) => Some(classify_serde_value_kind(value)),
_ => None,
};
let map_value_serializable_type = match &type_cat {
TypeCategory::Map(_, value) => get_serializable_type_name(value),
_ => None,
};
let record_value_kind = match &type_cat {
TypeCategory::Record(_, value) => Some(classify_serde_value_kind(value)),
_ => None,
};
let record_value_serializable_type = match &type_cat {
TypeCategory::Record(_, value) => get_serializable_type_name(value),
_ => None,
};
let wrapper_inner_kind = match &type_cat {
TypeCategory::Wrapper(inner) => Some(classify_serde_value_kind(inner)),
_ => None,
};
let wrapper_serializable_type = match &type_cat {
TypeCategory::Wrapper(inner) => get_serializable_type_name(inner),
_ => None,
};
let optional_inner_kind = match &type_cat {
TypeCategory::Optional(inner) => Some(classify_serde_value_kind(inner)),
_ => None,
};
let optional_serializable_type = match &type_cat {
TypeCategory::Optional(inner) => get_serializable_type_name(inner),
_ => None,
};
let deserialize_with_src = if opts.deserialize_with.is_some() {
opts.deserialize_with.clone()
} else {
let foreign_types = get_foreign_types();
let ft_match = TypeCategory::match_foreign_type(&field.ts_type, &foreign_types);
if let Some(error) = ft_match.error {
diagnostics.error(field.span, error);
}
if let Some(warning) = ft_match.warning {
diagnostics.warning(field.span, warning);
}
ft_match
.config
.and_then(|ft| ft.deserialize_expr.clone())
.map(|expr| rewrite_expression_namespaces(&expr))
.or_else(|| try_composite_foreign_deserialize(&field.ts_type))
};
let deserialize_with =
deserialize_with_src
.as_ref()
.and_then(|expr_src| match parse_ts_expr(expr_src) {
Ok(expr) => Some(*expr),
Err(err) => {
diagnostics.error(
field.span,
format!(
"@serde(deserializeWith): invalid expression for '{}': {err:?}",
field.name
),
);
None
}
});
let default_expr =
opts.default_expr
.as_ref()
.and_then(|expr_src| match parse_default_expr(expr_src) {
Ok(expr) => Some(expr),
Err(err) => {
diagnostics.error(
field.span,
format!(
"@serde({{default: ...}}): invalid expression for '{}': {err:?}",
field.name
),
);
None
}
});
Some(DeserializeField {
json_key,
field_name: field.name.clone(),
field_ident: ts_ident!(field.name.as_str()),
raw_cast_type: raw_cast_type(&field.ts_type, &type_cat),
ts_type: field.ts_type.clone(),
type_cat,
optional: field.optional || opts.default || opts.default_expr.is_some(),
has_default: opts.default || opts.default_expr.is_some(),
default_expr,
flatten: opts.flatten,
validators: opts.validators.clone(),
nullable_inner_kind,
array_elem_kind,
nullable_serializable_type,
deserialize_with,
decimal_format: opts.format.as_deref() == Some("decimal"),
array_elem_serializable_type,
set_elem_kind,
set_elem_serializable_type,
map_value_kind,
map_value_serializable_type,
record_value_kind,
record_value_serializable_type,
wrapper_inner_kind,
wrapper_serializable_type,
optional_inner_kind,
optional_serializable_type,
})
}
#[allow(dead_code)]
pub(super) fn generate_object_variant_deser_block(
variant: &ObjectVariant,
source_var: &str,
tag_field: &str,
full_type_name: &str,
) -> TsStream {
let mut lines = Vec::new();
lines.push("{".to_string());
lines.push(format!(
" const __obj = {} as Record<string, unknown>;",
source_var
));
lines.push(" const __inst: any = {};".to_string());
if let Some(tv) = &variant.tag_value {
lines.push(format!(" __inst[\"{}\"] = \"{}\";", tag_field, tv));
}
for field in &variant.fields {
let key = &field.json_key;
if field.optional {
lines.push(format!(
" if (\"{}\" in __obj && __obj[\"{}\"] !== undefined) {{",
key, key
));
generate_field_assignment(&mut lines, field, " ");
lines.push(" }".to_string());
} else {
lines.push(format!(" if (\"{}\" in __obj) {{", key));
generate_field_assignment(&mut lines, field, " ");
lines.push(" }".to_string());
}
}
lines.push(format!(
" ctx.trackForFreeze(__inst); return __inst as {};",
full_type_name
));
lines.push("}".to_string());
TsStream::from_string(lines.join("\n"))
}
#[allow(dead_code)]
pub(super) fn generate_field_assignment(
lines: &mut Vec<String>,
field: &DeserializeField,
indent: &str,
) {
let key = &field.json_key;
let fname = &field.field_name;
match &field.type_cat {
TypeCategory::Primitive => {
lines.push(format!(
"{}__inst.{} = __obj[\"{}\"] as {};",
indent, fname, key, field.ts_type
));
}
TypeCategory::Date => {
lines.push(format!(
"{}__inst.{} = typeof __obj[\"{}\"] === \"string\" ? new Date(__obj[\"{}\"] as string) : __obj[\"{}\"] as Date;",
indent, fname, key, key, key
));
}
TypeCategory::Serializable(type_name) => {
let deser_fn = nested_deserialize_fn_name(type_name);
lines.push(format!(
"{}__inst.{} = {}(__obj[\"{}\"], ctx) as {};",
indent, fname, deser_fn, key, field.ts_type
));
}
TypeCategory::Nullable(inner) => {
let inner_cat = TypeCategory::from_ts_type(inner);
match inner_cat {
TypeCategory::Date => {
lines.push(format!(
"{}__inst.{} = __obj[\"{}\"] === null ? null : (typeof __obj[\"{}\"] === \"string\" ? new Date(__obj[\"{}\"] as string) : __obj[\"{}\"] as Date);",
indent, fname, key, key, key, key
));
}
TypeCategory::Serializable(ser_name) => {
let deser_fn = nested_deserialize_fn_name(&ser_name);
lines.push(format!(
"{}__inst.{} = __obj[\"{}\"] === null ? null : {}(__obj[\"{}\"], ctx) as {};",
indent, fname, key, deser_fn, key, field.ts_type
));
}
_ => {
lines.push(format!(
"{}__inst.{} = __obj[\"{}\"] as {};",
indent, fname, key, field.raw_cast_type
));
}
}
}
TypeCategory::Array(inner) => {
let elem_kind = field.array_elem_kind.unwrap_or(SerdeValueKind::Other);
match elem_kind {
SerdeValueKind::PrimitiveLike => {
lines.push(format!(
"{}__inst.{} = __obj[\"{}\"] as {}[];",
indent, fname, key, inner
));
}
SerdeValueKind::Date => {
lines.push(format!(
"{}__inst.{} = Array.isArray(__obj[\"{}\"]) ? (__obj[\"{}\"] as any[]).map((item: any) => typeof item === \"string\" ? new Date(item) : item as Date) : [];",
indent, fname, key, key
));
}
_ => {
if let Some(ref elem_type) = field.array_elem_serializable_type {
let deser_fn = nested_deserialize_fn_name(elem_type);
lines.push(format!(
"{}__inst.{} = Array.isArray(__obj[\"{}\"]) ? (__obj[\"{}\"] as any[]).map((item: any) => {}(item, ctx)) : [];",
indent, fname, key, key, deser_fn
));
} else {
lines.push(format!(
"{}__inst.{} = __obj[\"{}\"] as {};",
indent, fname, key, field.raw_cast_type
));
}
}
}
}
_ => {
lines.push(format!(
"{}__inst.{} = __obj[\"{}\"] as {};",
indent, fname, key, field.raw_cast_type
));
}
}
}