About Monkey 2 › Forums › Monkey 2 Code Library › WIP Jsonifier!
This topic contains 4 replies, has 4 voices, and was last updated by
regulark 1 year, 1 month ago.
-
AuthorPosts
-
February 18, 2018 at 6:46 am #13671
Here’s my first attempt at a new ‘jsonifier’ system for converting data to/from json.
You’ll need a bleeding edge build of develop branch for the TypeInfo.NullValue feature.
This will eventually end up in the std module (there’s already the start of a jsonifier in there but that”ll go).
This demo shows the basic idea including the ability to jsonify cyclic data.
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338Namespace jsonifier#Import "<std>"#Reflect jsonifierUsing std..'Test class!Class CProperty D1:D()Return _d1Setter( d:D )_d1=dEndProperty D2:D()Return _d2Setter( d:D )_d2=dEndProperty Position:Vec2i()Return _posSetter( pos:Vec2i )_pos=posendProperty Lives:Int()Return _livesSetter( lives:Int )_lives=livesEndProperty Color:Color()Return _colorSetter( color:Color )_color=colorEndProperty LocalMatrix:AffineMat4f()Return _lmatrixSetter( lmatrix:AffineMat4f )_lmatrix=lmatrixEndOperator To:String()Return "Pos="+Position+", Lives="+LivesEndPrivateField _d1:DField _d2:DField _lives:IntField _pos:Vec2fField _color:Color=graphics.Color.RedField _lmatrix:AffineMat4f=New AffineMat4fEndClass DProperty C:C()Return _cSetter( c:C )_c=cEndPrivateField _c:CEnd'helper functions'Function ToJsonArray<T>:JsonArray( values:T[] ) Where T Implements INumericLocal jvalues:=New JsonValue[values.Length]For Local i:=0 Until values.Lengthjvalues[i]=New JsonNumber( values[i] )NextReturn New JsonArray( jvalues )End'helper function'Function ToArray<T>:T[]( jvalue:JsonValue ) Where T Implements INumericLocal jvalues:=jvalue.ToArray()Local values:=New T[jvalues.Length]For Local i:=0 Until values.Lengthvalues[i]=jvalues[i].ToNumber()NextReturn valuesEnd'base class for custom jsonifier 'extensions'.'Class JsonifierExtConst All:=New Stack<JsonifierExt>Method New()All.Add( Self )EndMethod Jsonify:JsonValue( value:Variant,jsonifier:Jsonifier ) AbstractMethod Dejsonify:Variant( jvalue:JsonValue,type:TypeInfo,jsonifier:Jsonifier ) AbstractEnd'A sample jsonifier extension - just does Vec2i for now.'Class StdJsonifierExt Extends JsonifierExtConst Instance:=New StdJsonifierExtMethod Jsonify:JsonValue( value:Variant,jsonifier:Jsonifier ) OverrideSelect value.TypeCase Typeof<Vec2i>Local v:=Cast<Vec2i>( value )Return ToJsonArray( New Int[]( v.x,v.y ) )Case Typeof<Vec2f>Local v:=Cast<Vec2f>( value )Return ToJsonArray( New Float[]( v.x,v.y ) )Case Typeof<Recti>Local v:=Cast<Recti>( value )Return ToJsonArray( New Int[]( v.min.x,v.min.y,v.max.x,v.max.y ) )Case Typeof<Rectf>Local v:=Cast<Rectf>( value )Return ToJsonArray( New Float[]( v.min.x,v.min.y,v.max.x,v.max.y ) )Case Typeof<Vec3f>Local v:=Cast<Vec3f>( value )Return ToJsonArray( New Float[]( v.x,v.y,v.z ) )Case Typeof<AffineMat4f>Local v:=Cast<AffineMat4f>( value )Return ToJsonArray( New Float[]( v.m.i.x,v.m.i.y,v.m.i.z, v.m.j.x,v.m.j.y,v.m.j.z, v.m.k.x,v.m.k.y,v.m.k.z, v.t.x,v.t.y,v.t.z ) )Case Typeof<Color>Local v:=Cast<Color>( value )Return ToJsonArray( New Float[]( v.r,v.g,v.b,v.a ) )EndReturn NullEndMethod Dejsonify:Variant( jvalue:JsonValue,type:TypeInfo,jsonifier:Jsonifier ) OverrideSelect typeCase Typeof<Vec2i>Local v:=ToArray<Int>( jvalue )Return New Vec2i( v[0],v[1] )Case Typeof<Vec2f>Local v:=ToArray<Float>( jvalue )Return New Vec2f( v[0],v[1] )Case Typeof<Recti>Local v:=ToArray<Int>( jvalue )Return New Recti( v[0],v[1],v[2],v[3] )Case Typeof<Rectf>Local v:=ToArray<Float>( jvalue )Return New Rectf( v[0],v[1],v[2],v[3] )Case Typeof<Vec3f>Local v:=ToArray<Float>( jvalue )Return New Vec3f( v[0],v[1],v[2] )Case Typeof<Color>Local v:=ToArray<Float>( jvalue )Return New Color( v[0],v[1],v[2],v[3] )Case Typeof<AffineMat4f>Local v:=ToArray<Float>( jvalue )Return New AffineMat4f( v[0],v[1],v[2], v[3],v[4],v[5], v[6],v[7],v[8], v[9],v[10],v[11] )EndReturn NullEndEndClass JsonifierMethod CreateReference:JsonObject( obj:Object )Local jobj:=New JsonObjectLocal id:="@"+(_jsonified.Count()+1)jobj.SetString( "$class",obj.InstanceType.Name )jobj.SetString( "$id",id )_jsonified[obj]=idReturn jobjEndMethod Jsonify:JsonValue( value:Variant )'handle primitive typesSelect value.TypeCase Typeof<Bool>Return New JsonBool( Cast<Bool>( value ) )Case Typeof<Int>Return New JsonNumber( Cast<Int>( value ) )Case Typeof<Float>Return New JsonNumber( Cast<Float>( value ) )Case Typeof<String>Return New JsonString( Cast<String>( value ) )End'handle referencesIf value.Type.Kind="Class"Local obj:=Cast<Object>( value )If Not obj Return JsonValue.NullValueLocal id:=_jsonified[obj]If id Return New JsonString( id )Endif'try custom jsonifiersFor Local jext:=Eachin JsonifierExt.AllLocal jvalue:=jext.Jsonify( value,Self )If jvalue Return jvalueNext'automated jsonification of classesSelect value.Type.KindCase "Class"Local obj:=Cast<Object>( value )Local jobj:=CreateReference( obj )If obj JsonifyProperties( obj,jobj,obj.InstanceType )Return jobjEndRuntimeError( "TODO: No jsonifier found for type '"+value.Type+"'" )Return NullEndMethod Dejsonify:Variant( jvalue:JsonValue,type:TypeInfo )'handle primitive typesSelect typeCase Typeof<Bool>Return jvalue.ToBool()Case Typeof<Int>Return Int( jvalue.ToNumber() )Case Typeof<Float>Return Float( jvalue.ToNumber() )Case Typeof<String>Return jvalue.ToString()End'handle referencesIf type.Kind="Class"If jvalue.IsNull Return type.NullValueIf jvalue.IsString Return _dejsonified[ jvalue.ToString() ]Endif'try custom jsonifiersFor Local jext:=Eachin JsonifierExt.AllLocal value:=jext.Dejsonify( jvalue,type,Self )If value Return valueNext'automated dejsonificationSelect type.KindCase "Class"Local jobj:=Cast<JsonObject>( jvalue )Local type:=TypeInfo.GetType( jobj.GetString( "$class" ) )Local id:=jobj.GetString( "$id" )Local ctor:=type.GetDecl( "New",Typeof<Void()> )Local obj:=Cast<Object>( ctor.Invoke( Null,Null ) )_dejsonified[id]=objDejsonifyProperties( obj,jobj,type )Return objEndRuntimeError( "TODO: No dejsonifier found for type '"+type+"'" )Return NullEndPrivateField _jsonified:=New Map<Object,String>Field _dejsonified:=New Map<String,Object>Method JsonifyProperties( obj:Object,jobj:JsonObject,type:TypeInfo )If type.SuperType JsonifyProperties( obj,jobj,type.SuperType )For Local d:=Eachin type.GetDecls()If d.Kind<>"Property" Continue'Note: Add DeclInfo.Access property so we can do public fields only?If Not d.Gettable Or Not d.Settable Continuejobj.SetValue( d.Name,Jsonify( d.Get( obj ) ) )NextEndMethod DejsonifyProperties( obj:Object,jobj:JsonObject,type:TypeInfo )If type.SuperType DejsonifyProperties( obj,jobj,type.SuperType )For Local d:=Eachin type.GetDecls()If d.Kind<>"Property" Continue'Note: Add DeclInfo.Access property so we can do public fields only?If Not d.Gettable Or Not d.Settable Continued.Set( obj,Dejsonify( jobj.GetValue( d.Name ),d.Type ) )NextEndEndFunction Main()Local jsonifier:=New JsonifierLocal c:=New Cc.Position=New Vec2i( 10,20 )c.Lives=10Local d:=New Dc.D1=dc.D2=dd.C=New Cd.C.D1=dLocal jobj:=jsonifier.Jsonify( c )Print jobj.ToJson()Local c2:=Cast<C>( jsonifier.Dejsonify( jobj,Typeof<Object> ) )Print c2EndFebruary 20, 2018 at 8:15 pm #13682Ooo! Is it possible to add blobs? (Basically just Databuffers) for adding any value? Like New JsonBlob(New DataBuffer(20))
Then we can save images, files, or really anything into a Json File. Would be amazingly convenient for sending data through TCP.
Great work Mark.So far I’m just converting a PNG to a JsonString and back again using a modified version of Nerobots datapacker.
February 24, 2018 at 4:38 am #13714Awesome! Time to scrap my w.i.p. serializer module. Good riddance…
February 25, 2018 at 11:47 pm #13728> Ooo! Is it possible to add blobs?
Json doesn’t support ‘blobs’, but you can roll your own.
For example, the gltf format allows you to use ‘data:’ URLs, and has an ‘accessor’ system that allows for data to be stored in external files. These aren’t actually part of json, but implemented ‘on top of’ json. I don’t see why we can’t implement/steal similar systems though.
Here’s a new WIP version that features an attempt at serializing constructors. Really rough and there’s lots to sort out here but it’s looking promising. Note that the LoadC and CreateC functions are really just ‘encapsulating’ the function call ‘C.Load( path:String )’ and the simple ctor ‘New C( Vec2i )’. I have no idea how this still will be implemented in practice yet though!
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500Namespace jsonifier#Import "<std>"#Reflect jsonifierUsing std..Function LoadC:C( path:String )Local scope:=TypeInfo.GetType( "jsonifier.C" )Local decl:=scope.GetDecl( "Load",Typeof<C(String)> )Local args:=New Variant[]( path )Local ctor:=New CtorInvokation( scope,decl,args )Local c:=Cast<C>( ctor.Execute() )Return cEndFunction CreateC:C( pos:Vec2i )Local scope:=TypeInfo.GetType( "jsonifier.C" )Local decl:=scope.GetDecl( "New",Typeof<Void(Vec2i)> )Local args:=New Variant[]( pos )Local ctor:=New CtorInvokation( scope,decl,args )Local c:=Cast<C>( ctor.Execute() )Return cEnd'Test class!Class CMethod New( pos:Vec2i )Print "New C, pos="+posPosition=posEndFunction Load:C( path:String )Print "C.Load, path="+pathReturn New CEndProperty Ctor:CtorInvokation()Return _ctorSetter( ctor:CtorInvokation )_ctor=ctorEndProperty D1:D()Return _d1Setter( d:D )_d1=dEndProperty D2:D()Return _d2Setter( d:D )_d2=dEndProperty Position:Vec2i()Return _posSetter( pos:Vec2i )_pos=posendProperty Lives:Int()Return _livesSetter( lives:Int )_lives=livesEndProperty Color:Color()Return _colorSetter( color:Color )_color=colorEndProperty LocalMatrix:AffineMat4f()Return _lmatrixSetter( lmatrix:AffineMat4f )_lmatrix=lmatrixEndOperator To:String()Return "Pos="+Position+", Lives="+LivesEndPrivateField _ctor:CtorInvokationField _d1:DField _d2:DField _lives:IntField _pos:Vec2fField _color:Color=graphics.Color.RedField _lmatrix:AffineMat4f=New AffineMat4fMethod New()EndEndClass DProperty C:C()Return _cSetter( c:C )_c=cEndPrivateField _c:CEndClass CtorInvokationMethod New( scope:TypeInfo,decl:DeclInfo,args:Variant[] )_scope=scope_decl=decl_args=argsEndProperty Scope:TypeInfo()Return _scopeEndProperty Decl:DeclInfo()Return _declEndProperty Args:Variant[]()Return _argsEndMethod Execute:Variant()Local inst:=_decl.Invoke( Null,_args )Local decl:=inst.Type.GetDecl( "Ctor" )If decl And decl.Kind="Property" And decl.Settable And decl.Gettable decl.Set( inst,Self )Return instEndPrivateField _scope:TypeInfoField _decl:DeclInfoField _args:Variant[]EndClass CtorJsonifier Extends JsonifierExtConst Instance:=New CtorJsonifierMethod Jsonify:JsonValue( value:Variant,jsonifier:Jsonifier ) OverrideSelect value.TypeCase Typeof<CtorInvokation>Local v:=Cast<CtorInvokation>( value )Local jobj:=New JsonObjectjobj.SetString( "scope",v.Scope.Name )jobj.SetString( "decl",v.Decl.Name )jobj.SetString( "type",v.Decl.Type )jobj.SetValue( "args",jsonifier.JsonifyArray( v.Args ) )Return jobjEndReturn NullEndMethod Dejsonify:Variant( jvalue:JsonValue,type:TypeInfo,jsonifier:Jsonifier ) OverrideSelect typeCase Typeof<CtorInvokation>Local jobj:=Cast<JsonObject>( jvalue )Local scope:=TypeInfo.GetType( jobj.GetString( "scope" ) )Local dname:=jobj.GetString( "decl" )Local dtype:=jobj.GetString( "type" )Local jargs:=jobj.GetArray( "args" )Local decl:DeclInfoFor Local tdecl:=Eachin scope.GetDecls( dname )If String(tdecl.Type)<>dtype Continuedecl=tdeclExitNextLocal args:=New Variant[jargs.Length]For Local i:=0 Until args.Lengthargs[i]=jsonifier.Dejsonify( jargs[i],decl.Type.ParamTypes[i] )NextLocal ctor:=New CtorInvokation( scope,decl,args )Return ctorEndReturn NullEndEnd'Function ToJson:JsonObject( inv:Invokation )'End'Function ToInvokation:Invokation( jobj:JsonObject )'End'base class for custom jsonifier 'extensions'.'Class JsonifierExtConst All:=New Stack<JsonifierExt>Method New()All.Add( Self )EndMethod Jsonify:JsonValue( value:Variant,jsonifier:Jsonifier ) AbstractMethod Dejsonify:Variant( jvalue:JsonValue,type:TypeInfo,jsonifier:Jsonifier ) AbstractEnd'A sample jsonifier extension - just does Vec2i for now.'Class StdJsonifierExt Extends JsonifierExtConst Instance:=New StdJsonifierExtMethod Jsonify:JsonValue( value:Variant,jsonifier:Jsonifier ) OverrideSelect value.TypeCase Typeof<Vec2i>Local v:=Cast<Vec2i>( value )Return jsonifier.JsonifyArray( New Int[]( v.x,v.y ) )Case Typeof<Vec2f>Local v:=Cast<Vec2f>( value )Return jsonifier.JsonifyArray( New Float[]( v.x,v.y ) )Case Typeof<Recti>Local v:=Cast<Recti>( value )Return jsonifier.JsonifyArray( new Int[]( v.min.x,v.min.y,v.max.x,v.max.y ) )Case Typeof<Rectf>Local v:=Cast<Rectf>( value )Return jsonifier.JsonifyArray( New Float[]( v.min.x,v.min.y,v.max.x,v.max.y ) )Case Typeof<Vec3f>Local v:=Cast<Vec3f>( value )Return jsonifier.JsonifyArray( New Float[]( v.x,v.y,v.z ) )Case Typeof<AffineMat4f>Local v:=Cast<AffineMat4f>( value )Return jsonifier.JsonifyArray( New Float[]( v.m.i.x,v.m.i.y,v.m.i.z, v.m.j.x,v.m.j.y,v.m.j.z, v.m.k.x,v.m.k.y,v.m.k.z, v.t.x,v.t.y,v.t.z ) )Case Typeof<Color>Local v:=Cast<Color>( value )Return jsonifier.JsonifyArray( New Float[]( v.r,v.g,v.b,v.a ) )EndReturn NullEndMethod Dejsonify:Variant( jvalue:JsonValue,type:TypeInfo,jsonifier:Jsonifier ) OverrideSelect typeCase Typeof<Vec2i>Local v:=jsonifier.DejsonifyArray<Int>( jvalue )Return New Vec2i( v[0],v[1] )Case Typeof<Vec2f>Local v:=jsonifier.DejsonifyArray<Float>( jvalue )Return New Vec2f( v[0],v[1] )Case Typeof<Recti>Local v:=jsonifier.DejsonifyArray<Float>( jvalue )Return New Recti( v[0],v[1],v[2],v[3] )Case Typeof<Rectf>Local v:=jsonifier.DejsonifyArray<Float>( jvalue )Return New Rectf( v[0],v[1],v[2],v[3] )Case Typeof<Vec3f>Local v:=jsonifier.DejsonifyArray<Float>( jvalue )Return New Vec3f( v[0],v[1],v[2] )Case Typeof<Color>Local v:=jsonifier.DejsonifyArray<Float>( jvalue )Return New Color( v[0],v[1],v[2],v[3] )Case Typeof<AffineMat4f>Local v:=jsonifier.DejsonifyArray<Float>( jvalue )Return New AffineMat4f( v[0],v[1],v[2], v[3],v[4],v[5], v[6],v[7],v[8], v[9],v[10],v[11] )EndReturn NullEndEndClass JsonifierMethod CreateReference:JsonObject( obj:Object )Local jobj:=New JsonObjectLocal id:="@"+(_jsonified.Count()+1)jobj.SetString( "$class",obj.InstanceType.Name )jobj.SetString( "$id",id )_jsonified[obj]=idReturn jobjEndMethod Jsonify:JsonValue( value:Variant )Local type:=value.Type'handle primitive typesSelect typeCase Typeof<Bool>Return New JsonBool( Cast<Bool>( value ) )Case Typeof<Int>Return New JsonNumber( Cast<Int>( value ) )Case Typeof<Float>Return New JsonNumber( Cast<Float>( value ) )Case Typeof<String>Return New JsonString( Cast<String>( value ) )End'handle referencesSelect type.KindCase "Class"Local obj:=Cast<Object>( value )If Not obj Return JsonValue.NullValueLocal id:=_jsonified[obj]If id Return New JsonString( id )End'try custom jsonifiersFor Local jext:=Eachin JsonifierExt.AllLocal jvalue:=jext.Jsonify( value,Self )If jvalue Return jvalueNext'automated jsonification of classesSelect type.KindCase "Class"Local obj:=Cast<Object>( value )Local jobj:=CreateReference( obj )If obj JsonifyProperties( obj,jobj,obj.InstanceType )Return jobjEndRuntimeError( "TODO: No jsonifier found for type '"+type+"'" )Return NullEndMethod Dejsonify:Variant( jvalue:JsonValue,type:TypeInfo )'handle primitive typesSelect typeCase Typeof<Bool>Return jvalue.ToBool()Case Typeof<Int>Return Int( jvalue.ToNumber() )Case Typeof<Float>Return Float( jvalue.ToNumber() )Case Typeof<String>Return jvalue.ToString()End'handle referencesSelect type.KindCase "Class"If jvalue.IsNull Return type.NullValueIf jvalue.IsString Return _dejsonified[ jvalue.ToString() ]End'try custom jsonifiersFor Local jext:=Eachin JsonifierExt.AllLocal value:=jext.Dejsonify( jvalue,type,Self )If value Return valueNext'automated dejsonificationSelect type.KindCase "Class"Local jobj:=Cast<JsonObject>( jvalue )Local type:=TypeInfo.GetType( jobj.GetString( "$class" ) )Local id:=jobj.GetString( "$id" )Local obj:ObjectIf jobj.Contains( "Ctor" )Local ctor:=Cast<CtorInvokation>( Dejsonify( jobj.GetValue( "Ctor" ),Typeof<CtorInvokation> ) )obj=Cast<Object>( ctor.Execute() )ElseLocal ctor:=type.GetDecl( "New",Typeof<Void()> )obj=Cast<Object>( ctor.Invoke( Null,Null ) )Endif_dejsonified[id]=objDejsonifyProperties( obj,jobj,type )Return objEndRuntimeError( "TODO: No dejsonifier found for type '"+type+"'" )Return NullEndMethod JsonifyArray<C>:JsonArray( values:C[] )Local jvalues:=New JsonArray( values.Length )For Local i:=0 Until jvalues.Lengthjvalues[i]=Jsonify( values[i])NextReturn jvaluesEndMethod DejsonifyArray<C>:C[]( jvalue:JsonValue )Local jvalues:=jvalue.ToArray()Local values:=New C[jvalues.Length]For Local i:=0 Until values.Lengthvalues[i]=Cast<C>( Dejsonify( jvalues[i],Typeof<C> ) )NextReturn valuesEndPrivateField _jsonified:=New Map<Object,String>Field _dejsonified:=New Map<String,Object>Method JsonifyProperties( obj:Object,jobj:JsonObject,type:TypeInfo )If type.SuperType JsonifyProperties( obj,jobj,type.SuperType )For Local d:=Eachin type.GetDecls()If d.Kind<>"Property" Continue'Note: Add DeclInfo.Access property so we can do public fields only?If Not d.Gettable Or Not d.Settable Continuejobj.SetValue( d.Name,Jsonify( d.Get( obj ) ) )NextEndMethod DejsonifyProperties( obj:Object,jobj:JsonObject,type:TypeInfo )If type.SuperType DejsonifyProperties( obj,jobj,type.SuperType )For Local d:=Eachin type.GetDecls()If d.Kind<>"Property" Continue'Note: Add DeclInfo.Access property so we can do public fields only?If Not d.Gettable Or Not d.Settable Continued.Set( obj,Dejsonify( jobj.GetValue( d.Name ),d.Type ) )NextEndEndFunction Main()Local jsonifier:=New JsonifierLocal c:=CreateC( New Vec2i( 100,200 ) )c.Position=New Vec2i( 10,20 )c.Lives=10Local d:=New Dc.D1=dc.D2=dd.C=LoadC( "asset::blah" )d.C.D1=dLocal jobj:=jsonifier.Jsonify( c )Print jobj.ToJson()Local c2:=Cast<C>( jsonifier.Dejsonify( jobj,Typeof<Object> ) )Print c2EndMarch 1, 2018 at 3:24 am #13787I was hoping to see this come around sometime, it will be quite useful. Thank you!
-
AuthorPosts
You must be logged in to reply to this topic.