use crate::json_value::JsonValue;
use crate::marshal::{register_typed_fn_1, register_typed_fn_2};
use crate::module_exports::{ModuleExports, ModuleParam};
use crate::type_schema::TypeSchemaRegistry;
use crate::typed_module_exports::{
ConcreteReturn, ConcreteType, TypedReturn, register_typed_function,
};
use shape_value::heap_value::HeapValue;
use shape_value::{KindedSlot, ValueSlot};
use std::sync::Arc;
const JSON_VARIANT_NULL: i64 = 0;
const JSON_VARIANT_BOOL: i64 = 1;
const JSON_VARIANT_INT: i64 = 2;
const JSON_VARIANT_NUMBER: i64 = 3;
const JSON_VARIANT_STR: i64 = 4;
const JSON_VARIANT_ARRAY: i64 = 5;
const JSON_VARIANT_OBJECT: i64 = 6;
fn build_json_enum_heap_value(value: serde_json::Value, json_schema_id: u64) -> HeapValue {
let (variant_id, payload_slot, payload_is_heap) = match value {
serde_json::Value::Null => (JSON_VARIANT_NULL, ValueSlot::none(), false),
serde_json::Value::Bool(b) => (JSON_VARIANT_BOOL, ValueSlot::from_bool(b), false),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
if !n.to_string().contains('.') {
return build_typed_object(
json_schema_id,
vec![
ValueSlot::from_int(JSON_VARIANT_INT),
ValueSlot::from_int(i),
],
0,
);
}
}
(
JSON_VARIANT_NUMBER,
ValueSlot::from_number(n.as_f64().unwrap_or(0.0)),
false,
)
}
serde_json::Value::String(s) => (
JSON_VARIANT_STR,
ValueSlot::from_string_arc(Arc::new(s)),
true,
),
serde_json::Value::Array(arr) => {
let element_ptrs: Vec<*const shape_value::TypedObjectStorage> = arr
.into_iter()
.map(|v| {
let hv = build_json_enum_heap_value(v, json_schema_id);
let to_ptr = match hv {
HeapValue::TypedObject(p) => p,
other => panic!(
"json: build_json_enum_heap_value must return \
TypedObject, got {:?}",
other.kind()
),
};
to_ptr.into_raw()
})
.collect();
let arr_ptr: *mut shape_value::v2::typed_array::TypedArray<
*const shape_value::TypedObjectStorage,
> = shape_value::v2::typed_array::TypedArray::<
*const shape_value::TypedObjectStorage,
>::from_slice(&element_ptrs);
(
JSON_VARIANT_ARRAY,
ValueSlot::from_u64(arr_ptr as u64),
true,
)
}
serde_json::Value::Object(map) => {
let mut data: shape_value::heap_value::HashMapData<
shape_value::heap_value::TypedObjectPtr,
> = shape_value::heap_value::HashMapData::new();
for (k, v) in map.into_iter() {
let nested = build_json_enum_heap_value(v, json_schema_id);
let to_ptr = match nested {
HeapValue::TypedObject(p) => p,
other => panic!(
"build_json_enum_heap_value must return TypedObject, got {:?}",
other.kind()
),
};
unsafe { data.insert(k.as_str(), to_ptr) };
}
let kref = shape_value::heap_value::HashMapKindedRef::TypedObject(Arc::new(data));
(
JSON_VARIANT_OBJECT,
ValueSlot::from_hashmap(Arc::new(kref)),
true,
)
}
};
let heap_mask = if payload_is_heap { 1u64 << 1 } else { 0u64 };
build_typed_object(
json_schema_id,
vec![ValueSlot::from_int(variant_id), payload_slot],
heap_mask,
)
}
fn build_typed_object(schema_id: u64, slots: Vec<ValueSlot>, heap_mask: u64) -> HeapValue {
let storage = shape_value::TypedObjectStorage::_new(
schema_id,
slots.into_boxed_slice(),
heap_mask,
Arc::from(Vec::<shape_value::NativeKind>::new().into_boxed_slice()),
);
HeapValue::TypedObject(shape_value::heap_value::TypedObjectPtr::new(storage))
}
fn serde_json_to_json_value(value: serde_json::Value) -> JsonValue {
match value {
serde_json::Value::Null => JsonValue::Null,
serde_json::Value::Bool(b) => JsonValue::Bool(b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
if !n.to_string().contains('.') {
return JsonValue::Int(i);
}
}
JsonValue::Number(n.as_f64().unwrap_or(0.0))
}
serde_json::Value::String(s) => JsonValue::String(s),
serde_json::Value::Array(arr) => {
JsonValue::Array(arr.into_iter().map(serde_json_to_json_value).collect())
}
serde_json::Value::Object(map) => {
let pairs: Vec<(String, JsonValue)> = map
.into_iter()
.map(|(k, v)| (k, serde_json_to_json_value(v)))
.collect();
JsonValue::Object(pairs)
}
}
}
fn build_field_slot_from_json(
value: &serde_json::Value,
field_type: &crate::type_schema::FieldType,
registry: &TypeSchemaRegistry,
json_schema_id: u64,
) -> Result<(ValueSlot, bool), String> {
use crate::type_schema::FieldType;
use serde_json::Value;
match (value, field_type) {
(Value::Null, _) => Ok((ValueSlot::none(), false)),
(Value::Bool(b), FieldType::Bool) => Ok((ValueSlot::from_bool(*b), false)),
(Value::Number(n), FieldType::I64) => {
Ok((ValueSlot::from_int(n.as_i64().unwrap_or(0)), false))
}
(Value::Number(n), FieldType::F64) | (Value::Number(n), FieldType::Decimal) => Ok((
ValueSlot::from_number(n.as_f64().unwrap_or(0.0)),
false,
)),
(Value::String(s), FieldType::String) => {
Ok((ValueSlot::from_string_arc(Arc::new(s.clone())), true))
}
(Value::Object(obj), FieldType::Object(type_name)) => {
if let Some(nested_schema) = registry.get(type_name) {
let nested_hv =
build_typed_object_from_json(nested_schema, obj, registry, json_schema_id)?;
Ok((heap_to_slot(nested_hv), true))
} else {
let json_hv =
build_json_enum_heap_value(Value::Object(obj.clone()), json_schema_id);
Ok((heap_to_slot(json_hv), true))
}
}
_ => {
let json_hv = build_json_enum_heap_value(value.clone(), json_schema_id);
Ok((heap_to_slot(json_hv), true))
}
}
}
fn heap_to_slot(hv: HeapValue) -> ValueSlot {
match hv {
HeapValue::TypedObject(ptr) => ValueSlot::from_typed_object_raw(ptr.into_raw()),
HeapValue::String(arc) => ValueSlot::from_string_arc(arc),
HeapValue::HashMap(kref) => ValueSlot::from_hashmap(Arc::new(kref)),
HeapValue::Decimal(arc) => ValueSlot::from_decimal(arc),
HeapValue::BigInt(arc) => ValueSlot::from_bigint(arc),
HeapValue::DataTable(arc) => ValueSlot::from_data_table(arc),
HeapValue::IoHandle(arc) => ValueSlot::from_io_handle(arc),
HeapValue::NativeView(arc) => ValueSlot::from_native_view(arc),
#[allow(deprecated)]
other => ValueSlot::from_heap(other),
}
}
fn build_typed_object_from_json(
schema: &crate::type_schema::TypeSchema,
map: &serde_json::Map<String, serde_json::Value>,
registry: &TypeSchemaRegistry,
json_schema_id: u64,
) -> Result<HeapValue, String> {
let num_fields = schema.fields.len();
let mut slots = vec![ValueSlot::none(); num_fields];
let mut heap_mask = 0u64;
for field in &schema.fields {
let wire = field.wire_name();
let (slot, is_heap) = if let Some(jv) = map.get(wire) {
build_field_slot_from_json(jv, &field.field_type, registry, json_schema_id)?
} else {
(ValueSlot::none(), false)
};
slots[field.index as usize] = slot;
if is_heap {
heap_mask |= 1u64 << field.index;
}
}
Ok(build_typed_object(
schema.id as u64,
slots,
heap_mask,
))
}
pub fn create_json_module() -> ModuleExports {
let mut module = ModuleExports::new("std::core::json");
module.description = "JSON parsing and serialization".to_string();
register_typed_fn_1::<_, Arc<String>>(
&mut module,
"parse",
"Parse a JSON string into Shape values",
"text",
"string",
ConcreteType::Result(Box::new(ConcreteType::JsonValue("Json".to_string()))),
|text: Arc<String>, _ctx| {
let parsed: serde_json::Value = serde_json::from_str(text.as_str())
.map_err(|e| format!("json.parse() failed: {}", e))?;
let result = serde_json_to_json_value(parsed);
Ok(TypedReturn::Ok(ConcreteReturn::JsonValue(result)))
},
);
register_typed_fn_2::<_, Arc<String>, f64>(
&mut module,
"__parse_typed",
"Parse a JSON string into a typed struct",
[("text", "string"), ("schema_id", "number")],
ConcreteType::Result(Box::new(ConcreteType::OpaqueTypedObject(
"any".to_string(),
))),
|text: Arc<String>, schema_id_f: f64, ctx| {
let schema_id = schema_id_f as u32;
let parsed: serde_json::Value = serde_json::from_str(text.as_str())
.map_err(|e| format!("json.__parse_typed() failed: {}", e))?;
let map = match parsed {
serde_json::Value::Object(m) => m,
_ => {
return Err("json.__parse_typed() requires a JSON object".to_string());
}
};
let schema = ctx
.schemas
.get_by_id(schema_id)
.ok_or_else(|| format!("json.__parse_typed(): unknown schema id {}", schema_id))?;
let json_schema = ctx.schemas.get("Json").ok_or_else(|| {
"json.__parse_typed() requires the `Json` enum schema (load std::core::json_value)"
.to_string()
})?;
let json_schema_id = json_schema.id as u64;
let result_hv = build_typed_object_from_json(schema, &map, ctx.schemas, json_schema_id)?;
Ok(TypedReturn::Ok(ConcreteReturn::OpaqueTypedObject(Arc::new(
result_hv,
))))
},
);
register_typed_function(
&mut module,
"stringify",
"Serialize a Shape value to a JSON string",
vec![
ModuleParam {
name: "value".to_string(),
type_name: "any".to_string(),
required: true,
description: "Value to serialize".to_string(),
..Default::default()
},
ModuleParam {
name: "pretty".to_string(),
type_name: "bool".to_string(),
required: false,
description: "Pretty-print with indentation (default: false)".to_string(),
default_snippet: Some("false".to_string()),
..Default::default()
},
],
ConcreteType::Result(Box::new(ConcreteType::String)),
|args, _ctx| {
let _value = args
.first()
.ok_or_else(|| "json.stringify() requires a value argument".to_string())?;
let _pretty = args.get(1).map(|a| a.slot().as_bool()).unwrap_or(false);
Ok(TypedReturn::Err(ConcreteReturn::String(
"json.stringify() pending N7 (HeapValue→JSON) — see ADR-006 §2.7.4".to_string(),
)))
},
);
register_typed_function(
&mut module,
"is_valid",
"Check if a string is valid JSON",
vec![ModuleParam {
name: "text".to_string(),
type_name: "string".to_string(),
required: true,
description: "String to validate as JSON".to_string(),
..Default::default()
}],
ConcreteType::Bool,
|args, _ctx| {
let slot = args
.first()
.ok_or_else(|| "json.is_valid() requires a string argument".to_string())?;
let text = slot_as_string(slot)
.ok_or_else(|| "json.is_valid() requires a string argument".to_string())?;
let valid = serde_json::from_str::<serde_json::Value>(text.as_str()).is_ok();
Ok(TypedReturn::Concrete(ConcreteReturn::Bool(valid)))
},
);
module
}
fn slot_as_string(slot: &KindedSlot) -> Option<Arc<String>> {
let bits = slot.slot().raw();
if bits == 0 {
return None;
}
unsafe {
let arc = Arc::<String>::from_raw(bits as *const String);
let cloned = arc.clone();
std::mem::forget(arc);
Some(cloned)
}
}