#if TOOLS using Godot; namespace Rokojori.Tools { using Rokojori; // Two-way mapping between Godot Variant and the addon's own JSON types. // Sub-resources are referenced by resource_path; an embedded sub-resource with // no saved path round-trips as a tagged placeholder rather than its real data - // not needed by the first 4 routes, revisit if a fragment ever needs it. public static class VariantJson { public static JSONData ToJson( Variant v ) { switch ( v.VariantType ) { case Variant.Type.Nil: return new JSONValue(); case Variant.Type.Bool: return new JSONValue( v.AsBool() ); case Variant.Type.Int: return new JSONValue( (double) v.AsInt64() ); case Variant.Type.Float: return new JSONValue( v.AsDouble() ); case Variant.Type.String: case Variant.Type.StringName: return new JSONValue( v.AsString() ); case Variant.Type.NodePath: return new JSONValue( v.AsNodePath().ToString() ); case Variant.Type.Vector2: return Vector2ToJson( v.AsVector2() ); case Variant.Type.Vector2I: var v2i = v.AsVector2I(); return Vector2ToJson( new Vector2( v2i.X, v2i.Y ) ); case Variant.Type.Vector3: return Vector3ToJson( v.AsVector3() ); case Variant.Type.Vector3I: var v3i = v.AsVector3I(); return Vector3ToJson( new Vector3( v3i.X, v3i.Y, v3i.Z ) ); case Variant.Type.Vector4: return Vector4ToJson( v.AsVector4() ); case Variant.Type.Color: return ColorToJson( v.AsColor() ); case Variant.Type.Array: return ArrayToJson( v.AsGodotArray() ); case Variant.Type.Dictionary: return DictionaryToJson( v.AsGodotDictionary() ); case Variant.Type.Object: return ObjectToJson( v.AsGodotObject() ); default: // Not modeled explicitly (Rid, Callable, Signal, Transform*, Basis, Plane, Quaternion, Projection, Aabb, Rect2...). // Falls back to a readable string so round-tripping these doesn't crash - just loses write support. return new JSONValue( v.ToString() ); } } public static Variant FromJson( JSONData data, Variant.Type targetType ) { if ( data == null || data.isNull ) { return new Variant(); } switch ( targetType ) { case Variant.Type.Nil: // Untyped [Export] Variant field - no declared type to convert against, shape-sniff instead. return FromJsonInferred( data ); case Variant.Type.Bool: return data.booleanValue; case Variant.Type.Int: return data.longValue; case Variant.Type.Float: return data.numberValue; case Variant.Type.String: return data.stringValue; case Variant.Type.StringName: return new StringName( data.stringValue ); case Variant.Type.NodePath: return new NodePath( data.stringValue ); case Variant.Type.Vector2: return JsonToVector2( data.AsObject() ); case Variant.Type.Vector2I: var v2 = JsonToVector2( data.AsObject() ); return new Vector2I( (int) v2.X, (int) v2.Y ); case Variant.Type.Vector3: return JsonToVector3( data.AsObject() ); case Variant.Type.Vector3I: var v3 = JsonToVector3( data.AsObject() ); return new Vector3I( (int) v3.X, (int) v3.Y, (int) v3.Z ); case Variant.Type.Vector4: return JsonToVector4( data.AsObject() ); case Variant.Type.Color: return JsonToColor( data.AsObject() ); case Variant.Type.Array: return JsonToArray( data.AsArray() ); case Variant.Type.Dictionary: return JsonToDictionary( data.AsObject() ); case Variant.Type.Object: return JsonToResource( data.AsObject() ); default: RJLog.Error( "MCP VariantJson: write not supported for Variant type", targetType ); return new Variant(); } } // Used for Array/Dictionary element values, which have no declared Variant type of their own - // shape-sniffs the JSON (keys x/y/z/w, r/g/b/a, __resourcePath) rather than a real type lookup. public static Variant FromJsonInferred( JSONData data ) { if ( data == null || data.isNull ) { return new Variant(); } if ( data.isBoolean ) { return data.booleanValue; } if ( data.isNumber ) { return data.numberValue; } if ( data.isString ) { return data.stringValue; } if ( data.isArray ) { return JsonToArray( data.AsArray() ); } if ( data.isObject ) { var obj = data.AsObject(); if ( obj.Contains( "__resourcePath" ) ) { return JsonToResource( obj ); } if ( obj.Contains( "x" ) && obj.Contains( "y" ) && obj.Contains( "z" ) && obj.Contains( "w" ) ) { return JsonToVector4( obj ); } if ( obj.Contains( "r" ) && obj.Contains( "g" ) && obj.Contains( "b" ) ) { return JsonToColor( obj ); } if ( obj.Contains( "x" ) && obj.Contains( "y" ) && obj.Contains( "z" ) ) { return JsonToVector3( obj ); } if ( obj.Contains( "x" ) && obj.Contains( "y" ) ) { return JsonToVector2( obj ); } return JsonToDictionary( obj ); } return new Variant(); } public static JSONObject Vector2ToJson( Vector2 v ) { var o = new JSONObject(); o.Set( "x", (double) v.X ); o.Set( "y", (double) v.Y ); return o; } public static JSONObject Vector3ToJson( Vector3 v ) { var o = new JSONObject(); o.Set( "x", (double) v.X ); o.Set( "y", (double) v.Y ); o.Set( "z", (double) v.Z ); return o; } public static JSONObject Vector4ToJson( Vector4 v ) { var o = new JSONObject(); o.Set( "x", (double) v.X ); o.Set( "y", (double) v.Y ); o.Set( "z", (double) v.Z ); o.Set( "w", (double) v.W ); return o; } public static JSONObject ColorToJson( Color c ) { var o = new JSONObject(); o.Set( "r", (double) c.R ); o.Set( "g", (double) c.G ); o.Set( "b", (double) c.B ); o.Set( "a", (double) c.A ); return o; } public static Vector2 JsonToVector2( JSONObject o ) { if ( o == null ) return Vector2.Zero; return new Vector2( (float) o.GetNumberOr( "x", 0 ), (float) o.GetNumberOr( "y", 0 ) ); } public static Vector3 JsonToVector3( JSONObject o ) { if ( o == null ) return Vector3.Zero; return new Vector3( (float) o.GetNumberOr( "x", 0 ), (float) o.GetNumberOr( "y", 0 ), (float) o.GetNumberOr( "z", 0 ) ); } public static Vector4 JsonToVector4( JSONObject o ) { if ( o == null ) return Vector4.Zero; return new Vector4( (float) o.GetNumberOr( "x", 0 ), (float) o.GetNumberOr( "y", 0 ), (float) o.GetNumberOr( "z", 0 ), (float) o.GetNumberOr( "w", 0 ) ); } public static Color JsonToColor( JSONObject o ) { if ( o == null ) return Colors.White; return new Color( (float) o.GetNumberOr( "r", 1 ), (float) o.GetNumberOr( "g", 1 ), (float) o.GetNumberOr( "b", 1 ), (float) o.GetNumberOr( "a", 1 ) ); } public static JSONArray ArrayToJson( Godot.Collections.Array array ) { var result = new JSONArray(); foreach ( var item in array ) { result.Push( ToJson( item ) ); } return result; } public static Godot.Collections.Array JsonToArray( JSONArray array ) { var result = new Godot.Collections.Array(); if ( array == null ) { return result; } foreach ( var item in array.values ) { result.Add( FromJsonInferred( item ) ); } return result; } public static JSONObject DictionaryToJson( Godot.Collections.Dictionary dict ) { var result = new JSONObject(); foreach ( var key in dict.Keys ) { result.Set( key.AsString(), ToJson( dict[ key ] ) ); } return result; } public static Godot.Collections.Dictionary JsonToDictionary( JSONObject o ) { var result = new Godot.Collections.Dictionary(); if ( o == null ) { return result; } for ( var i = 0; i < o.keys.Count; i++ ) { result[ o.keys[ i ] ] = FromJsonInferred( o.values[ i ] ); } return result; } public static JSONData ObjectToJson( GodotObject obj ) { if ( obj == null ) { return new JSONValue(); } var wrapper = new JSONObject(); if ( obj is Resource resource ) { if ( ! string.IsNullOrEmpty( resource.ResourcePath ) ) { wrapper.Set( "__resourcePath", resource.ResourcePath ); } else { wrapper.Set( "__embeddedResourceType", resource.GetClass() ); } return wrapper; } return new JSONValue( obj.ToString() ); } public static Resource JsonToResource( JSONObject o ) { if ( o == null || ! o.Contains( "__resourcePath" ) ) { return null; } var path = o.GetString( "__resourcePath" ); return ResourceLoader.Load( path ); } } } #endif