DCodec Sample Code
-
Get your schema. Generate a schema key (DKey).
Don't have a schema yet? No problem, use no schema, or use JSON to create one at JSON2ASN.
Schema must comply with the Dynamic Profile (check it at Analyzer). - Get the DCodec. Create an instance of Oss.DCodec JSON or DER. Load the schema (optionally).
- Start encoding/decoding.
DCodec (Dynamic Codec) is capable of doing three things dynamically:
- Dynamic loading of a schema at runtime.
- Dynamic encoding and decoding of data that partially match the schema and/or the bindings.
-
Dynamic bindings, i.e. the data objects that are created at runtime and support C#
dynamic
type.
DCodec deals with three kids of input/output objects:
BINDINGS (C#) | SCHEMA (ASN.1) | MESSAGE (JSON or DER) |
dynamic, DValue, POCO (predefined) | schema-less, schema-based | streams, strings, byte arrays |
// Hello World shows how to encode/decode/validate (serialize/deserialize as JSON or DER). // The data in this example is a "Hello World!" string, and the schema constrains // the string length to the range of 5..12 characters: // // Schema DEFINITIONS AUTOMATIC TAGS::= BEGIN // Type ::= SEQUENCE // { // hello UTF8String (SIZE (5..12)) -- length in UTF8 chars // } // END
// load the schema (use https://asn1.io/dynamic/DKey.aspx to get a DKey for your schema) var schema = new Oss.DCodec.DSchema("Zb5qQllrUFRhNcBlGA22SiGfaZEk+xA0Jw2w+UPWz11J0VGfMQd6qYG3/xAAEA55VqDUvwpnQSCcnQpy2OntMZZS5rdQLsGLN04yyQ5Sbj88DzWwzWCXkGo2F9P5LnMh4NryPkl3BLpXdbaR8mrm0ux28HLgSuRB/B5ZiOjKxyOmA9uLmGpWPwqIH98C718Y1RxnIXurnLE5H0u01qwDpelujU+5YKG5CTHu3RvqVat5j2LHWWAKRiFxFJSMX2SngF27FjdnZzceDh19xndBJfBROnzOmcHRZ/KdiWP80SJKsRDpgRIUjzAy9yXXdSsZ9jzTpz00lEaXMfsbFXs8GJ2pxw/LDGkJfpcxFwg6OcovUwM4CvkvaLygjwPlGIndtYdcr5ezbibPN1i4sPRwNWNTOgf+6rnV8UHLi3N9FEfW5xO0BJRZlliv/7f+gDLmAPEqg7d3ZJn9FEpqqjrJpXL0PVCmFR1RuaT0Sw6YqgRtQCK/k473ClXaXg4OGh8btOmGOZsaS5HHPyBV5pyIpnJIo+gymDnYIOjB9KBhdaDk6xtEw4DZoiKR2Mc7MGzdpziU0coFbUaunWN5TTxlC/Ww81kpOvHkVAguqBdI7RnKiqszuziR42W/4TXeumLd0tEYRFxrUA=="); var type = schema.FindType("Schema.Type"); // create and set the data object (dynamically) var dataOut = new Oss.DCodec.DValueObject() { {"hello", "Hello World"} }; // valid for the above schema, i.e. UTF8String (SIZE (5..12)) // Create a codec (either JSON or DER) var codec = new Oss.DCodec.JsonCodec(); // or .DerCodec() // NOTE: validation is possible in two ways: 1) during Encode()/Decode() or 2) by invoking Validate() // Encode/Decode with the SchemaType argument will validate the data against the provided type var msg = codec.Encode(dataOut, type); // .... send/receive JSON or DER codec.DecoderOptions.ValidateConstraints = true; // Constraints validation during decoding is optional dynamic dataIn = codec.Decode(msg, type); Console.WriteLine(dataIn.Hello.ToString()); // Validating data directly string json = "{'hello':'Hello World!!!!!'}"; // too long var conflicts = codec.Validate(json, type); // validate a string or a data binding object foreach (var c in conflicts) Console.WriteLine("Invalid data: " + c); /* Output: * Hello World * Invalid data: ConstraintViolation: Schema.Type.hello value=Hello World!!!!! (constraint: Schema.Type(SIZE(5..12)); value size: 16) */
// This example shows how to decode/encode a JSON message without using a schema. // Schema-less scenario is suitable for early stages of app/protocol development, // while data structures are still taking shape. // Once the data are more or less mature a schema can be defined for them // (e.g. by using this helper tool https://asn1.io/json2asn).
var myCodec = new Oss.DCodec.JsonCodec(); // Basic example: JSON message includes expected fields string json = "{ 'Name':'Falcon', 'Speed': { 'kmph':50000 }}"; var rocket = myCodec.Decode(json); if (rocket["Speed"]["kmph"] > 28968 ) Console.WriteLine(rocket["Name"] + " will leave the orbit.."); /* output: Falcon will leave the orbit.. */
// More complex example: handling optional fields and special keys string jsonIn = "{ 'Name':'Falcon', 'Hi-Message':'Hi there', 'Speed': { 'mph':18000 }, 'Payload': [ 'car', 'GPS unit' ]} "; dynamic rocket2 = myCodec.Decode(jsonIn); // mandatory fields Console.WriteLine("Name: " + rocket2.Name); Console.WriteLine("Message: " + rocket2["Hi-Message"]); // can't use dot notation (avoid special chars in JSON keys) Console.WriteLine("Speed: " + rocket2.Speed.mph.ToString()); // optional fields if (rocket2.ContainsKey("Payload")) // checking the key existence for optional foreach (var p in rocket2.Payload) Console.WriteLine("Payload: " + p); Console.WriteLine("Weight: " + (rocket2.Weight ?? "absent")); // Using operator ?? for optional Console.WriteLine("Launch: " + rocket2.Launch?.Location); // Using operator ?. for optional // modify and re-encode rocket2["Hi-Message"] = "Hello"; rocket2.Speed.mph = null; // use encoder option EncodeAbsentComponents to drop/include null fields rocket2.Speed.kmph = 0; // this field is new/added. myCodec.EncoderOptions.EncodeAbsentComponents = false; string jsonOut = myCodec.Encode(rocket2); Console.WriteLine(jsonOut); /* output: Name: Falcon Message: Hi there Speed: 18000 Payload: car Payload: GPS unit Weight: absent Launch: {"Name":"Falcon","Hi-Message":"Hello","Speed":{"kmph":0},"Payload":["car","GPS unit"]} */
// Let's "explore" the data (#traverse, #iterate) string jsonMsg = "{ 'Name':'Falcon', 'Message':'Hi there', 'Speed': { 'mph':18000 }, 'Payload': [ 'car', 'GPS unit' ] }"; var dv = myCodec.Decode(jsonMsg); foreach (var field in dv.Iterator.DescendantsAndSelf) { if (field.Value.DataType == Oss.DCodec.DDataType.Array) Console.WriteLine(field.Identifier + "(array):"); else if (field.Value.DataType == Oss.DCodec.DDataType.Object) Console.WriteLine(field.Identifier + "(object):"); else Console.WriteLine(" " + field.Identifier + ":" + field.Value.ToString()); /* Output (object): Name:Falcon Message:Hi there Speed(object): mph:18000 Payload(array): 0:car 1:GPS unit */ }
// World-Schema DEFINITIONS EXPLICIT TAGS ::= // BEGIN // Rocket ::= SEQUENCE // { // name [10] UTF8String(SIZE(1..16)), // message [11] UTF8String DEFAULT "Hello World" , // fuel [12] ENUMERATED { solid, liquid, gas, hybrid}, // speed [13] CHOICE // { // mph [20] INTEGER, // kmph [21] INTEGER // } OPTIONAL, // payload [14] SEQUENCE OF UTF8String // } // END using System.IO;
// The above schema was compiled into a DKey which is loaded into DCodec at runtime. See https://asn1.io/dynamic/DKey.aspx using (Stream sf = File.OpenRead("myschema.dkey")) { var mySchema = new Oss.DCodec.DSchema(sf); var inType = mySchema.FindType("World-Schema.Rocket"); var inMsg = "{'Name':'Falcon', 'Fuel':'solid', 'Payload':['none']}"; var myCodec = new Oss.DCodec.JsonCodec(); var outData = myCodec.Decode(inMsg, inType); Console.WriteLine("Name=" + outData["Name"]); // Message is absent in JSON, so the default is taken from the schema if (outData.ContainsKey("Message")) Console.WriteLine("Message=" + outData["Message"]); Console.WriteLine("Fuel=" + outData["Fuel"]); // Speed is absent in JSON, otherwise it'd be printed as ={key:value}, where key is either "mph" or "kmph" if (outData.ContainsKey("Speed")) Console.WriteLine("Speed=" + outData["Speed"]); Console.WriteLine("Payload=" + outData["Payload"]); /* Out: * Name=Falcon * Message=Hello World * Fuel=solid * Payload=["none"] */ }
// This example shows how to use a schema to validate messages, i.e. ensure // that the message contains all required fields and meets the constraints (if any). // Note, the example is in JSON, but DER messages will work the same way. // // DCodec also allows dynamic scenarios, where the schema and a message match // partially (see dynamic scenarios, e.g. schema evolution and partial decoding) // // MySchema DEFINITIONS AUTOMATIC TAGS::= BEGIN // Rocket ::= SEQUENCE // { // name UTF8String (SIZE (4..16)), // speed INTEGER (0..1000), // weight REAL (0..1000) OPTIONAL, // units ENUMERATED { metric, imperial } // } // END
// Load a schema, then decode and validate messages (some valid, some not) var myCodec = new Oss.DCodec.JsonCodec(); // load the above schema (use https://asn1.io/dynamic/DKey.aspx to get the schema key) var mySchema = new Oss.DCodec.DSchema("--- use https://asn1.io/dynamic/DKey.aspx to generate and paste the above schema key here ---"); var myType = mySchema.FindType("MySchema.Rocket"); // a valid message var goodMsg = "{'name':'Falcon', 'speed':0, 'units':'metric'}"; // invalid messages var badMsg1 = "{'name':'Falcon' }"; // missing required fields var badMsg2 = "{'name':'xxx', 'speed':0, 'units':'metric'}"; // too short name var badMsg3 = "{'name':'Falcon', 'speed':0, 'units':'knots' }"; // unknown units // decode and validate myCodec.DecoderOptions.ValidateConstraints = true; var outData = myCodec.Decode(goodMsg, myType); try { outData = myCodec.Decode(badMsg1, myType); } catch (Exception ex) { Console.WriteLine(ex.Message); // 0808E: The DCodec operation encountered an unhandled conflict.. SchemaExtra: MySchema.Rocket.speed (missing required field from message) } try { outData = myCodec.Decode(badMsg2, myType); } catch (Exception ex) { Console.WriteLine(ex.Message); // 0808E: The DCodec operation encountered an unhandled conflict.. ConstraintViolation: MySchema.Rocket.name value=xxx (constraint: MySchema.Rocket(SIZE(4..16)); value size: 3) } try { outData = myCodec.Decode(badMsg3, myType); } catch (Exception ex) { Console.WriteLine(ex.Message); // 0808E: The DCodec operation encountered an unhandled conflict..SchemaMissing: MySchema.Rocket.units.knots value = knots(identifier not found in schema type) }
// This example shows how to work with DValue - a dynamic type to hold your data. // Data objects are often called bindings, since they bind fields of your message with your code. // Data objects can be either pre-defined at compile time (POCO) or created dynamically at runtime. // There is trade-off between them, here is how they compare: // // IntelliSense Strong typing Access syntax // User-defined/POCO full yes obj.Prop // Dynamic/DValue/C# dynamic none no obj["Prop"] (C# dynamic obj.Prop) using System.Linq; using System.Collections.Generic; public class Fruit { public string Name { get; set; } public int Quantity { get; set; } }
var myCodec = new Oss.DCodec.JsonCodec(); // various ways to create and init DValues var minotaur = Oss.DCodec.DValue.CreateObject().Add("name", "Minotaur"); // Add(key,value) var atlas = new Oss.DCodec.DValueObject(); atlas["name"] = "Atlas"; // ["key"] = value var delta = new Oss.DCodec.DValueObject { {"name", "Delta"} }; // object initializer var falcon9 = new Oss.DCodec.DValueObject // object initializer { { "name", "Falcon 9" }, { "speed", new Oss.DCodec.DValueChoice("kmph", 50000) }, { "payload", new Oss.DCodec.DValueArray { "Car", "GPS" } } }; dynamic taurus = new Oss.DCodec.DValueObject(); // C# dynamic wraps a DValue taurus.name = "Taurus"; // C# dynamic allows dot notation dynamic athena = Oss.DCodec.DValue.CreateObject(); athena.name = "Athena"; var titan = (Oss.DCodec.DValueObject) myCodec.Decode("{ 'Name':'Titan' }"); // Initializing with a JSON string // Let's encode and print var rockets = new List<Oss.DCodec.DValue>() { falcon9, atlas, minotaur, delta, taurus, titan, athena }; foreach (var r in rockets) Console.WriteLine(myCodec.Encode(r)); /* output: {"name":"Falcon 9","speed":{"kmph":50000},"payload":["Car","GPS"]} {"name":"Atlas"} {"name":"Minotaur"} {"name":"Delta"} {"name":"Taurus"} {"Name":"Titan"} {"name":"Athena"} */
// C# dynamic type works well with JSON, when JSON keys contain no special characters (so keys are used for property names) dynamic rocket = myCodec.Decode("{ 'Name':'Falcon', 'Message':'Hi there', 'Speed': { 'mph':18000 }, 'Payload': [ 'car', 'GPS unit' ] }"); Console.WriteLine("Name: " + rocket.Name); Console.WriteLine("Message: " + rocket["Message"]); // avoid special chars in a key Console.WriteLine("Speed: " + rocket.Speed.mph); foreach (var p in rocket.Payload) Console.WriteLine("Payload: " + p); Console.WriteLine("Weight: " + (rocket.Weight ?? "??")); /* output: Name: Falcon Message: Hi there Speed: 18000 Payload: car Payload: GPS unit Weight: ?? */
// creating and encoding a DValue is as easy dynamic myRocket = new Oss.DCodec.DValueObject(); myRocket.Name = "Space"; myRocket.Message = "Hello ET"; myRocket.Speed = new Oss.DCodec.DValueChoice("mph", 1000); myRocket.Payload = new Oss.DCodec.DValueArray("My car"); myRocket.Payload.Add("My Radio"); string msg = myCodec.Encode(myRocket); Console.WriteLine(msg); /* output: * {"Name":"Space","Message":"Hello ET","Speed":{"mph":1000},"Payload":["My car","My Radio"]} */
// DValue allows to explore dynamic data (e.g. to find unexpected or optional fields) var falcon = (Oss.DCodec.DValueObject) myCodec.Decode(@"{ 'Name':'Falcon', 'Message':'Hi there', 'Speed': { 'mph':18000 }, 'Payload': [ 'car', 'GPS unit' ], 'Weight': 151.419 }"); // enumerate children/traverse the data tree foreach (var elem in falcon.Iterator.Descendants) Console.WriteLine($"1: {elem.Indent}{elem.Identifier}:{elem.Value}"); /* Output: 1: Name:Falcon 1: Message:Hi there 1: Speed:{"mph":18000} 1: mph:18000 1: Payload:["car","GPS unit"] 1: 0:car 1: 1:GPS unit 1: Weight:151.419 */ // enumerate generic collection foreach (var field in falcon) Console.WriteLine($"2: {field.Key}:{field.Value}"); /* Output: 2: Name:Falcon 2: Message:Hi there 2: Speed:{"mph":18000} 2: Payload:["car","GPS unit"] 2: Weight:151.419 */ // check for a field presence if (falcon.ContainsKey("Weight")) Console.WriteLine("found Weight: " + falcon["Weight"]); else Console.WriteLine("not found Weight"); /* Output: found Weight: 151.419 */ // find decimal fields via LINQ (note, it's bound to C# double by default) var numbers = from field in falcon.Iterator.Descendants where field.Value.DataType == Oss.DCodec.DDataType.Double select field.Identifier; Console.WriteLine("Decimal fields:"); foreach (var id in numbers) Console.WriteLine(" " + id); /* Output: Decimal fields: Weight */
// use a DValue as an intermediate object to customize serialization of a POCO // Let's say we have a POCO Fruit // public class Fruit // { // public string Name { get; set; } // public int Quantity { get; set; } // } var apple = new Fruit { Name = "Apple", Quantity = 100 }; var orange = new Fruit { Name = "Orange", Quantity = 200 }; var fruits = new List<Fruit>() { apple, orange}; // fruits will be serialized as [{ "Name":"Apple", "Quantity":100},{ "Name":"Orange", "Quantity":200}] // but we prefer a shorter form [{ "Apple":100 }, { "Orange":200}] // let's do it dynamically via a intermediate DValueArray var fruitesTmp = new Oss.DCodec.DValueArray(fruits.Select(item => new Oss.DCodec.DValueObject() { { item.Name, item.Quantity } })); string jsonArray = myCodec.Encode(fruitesTmp); Console.WriteLine(jsonArray); /* Output * [{"Apple":100},{"Orange":200}] */
// DValueChoice - one of many, e.g. either kmph or mph dynamic speed = new Oss.DCodec.DValueChoice("kmph", 28968); Console.WriteLine(myCodec.Encode(speed)); speed.mph = 18000; // switch to miles per hour Console.WriteLine(myCodec.Encode(speed)); /* Output * {"kmph":28968} * {"mph":18000} */
// This example shows how to work with pre-defined bindings (user defined data objects), // aka POCO https://en.wikipedia.org/wiki/Plain_old_CLR_object using System.Collections.Generic; // Define our data to be encoded/decoded public class Rocket { public string Name { get; set; } [Oss.DCodec.DField(CustomName = "Message")] // DField attribute specifies a name to match JSON key public string Note { get; set; } public Speed Speed; // Binding for ASN.1 type CHOICE public List<string> Payload { get; set; } } // let DCodec know that this is a special class bound to ASN.1 CHOICE. // Choice of either kilometers per hour or miles per hour, but not both. [Oss.DCodec.DChoice] public class Speed { public int? Kmph { get; set; } public int? Mph { get; set; } }
// Encode/Decode a Rocket var myCodec = new Oss.DCodec.JsonCodec(); var NewRocket = new Rocket { Name = "Falcon Heavy", Note = "I'm here", Speed = new Speed { Mph = 18000 } }; myCodec.EncoderOptions.Formatting = Oss.DCodec.JsonFormatting.Indented; string msg = myCodec.Encode(NewRocket); Console.WriteLine(msg); /* output: { "Name":"Falcon Heavy", "Message":"I'm here", "Speed":{ "Mph":18000 } } */ // Decode a Rocket var jsonMsg = @"{ 'Name':'Falcon', 'Message':'Hi there', 'Speed': { 'Mph':18000 }, 'Payload': [ 'GPS unit', 'Car' ] }"; var rocket = myCodec.Decode<Rocket>(jsonMsg); Console.WriteLine("Name: " + rocket.Name); Console.WriteLine("Message: " + rocket.Note); if (rocket.Speed.Mph != null) Console.WriteLine("Speed (mph): " + rocket.Speed.Mph); else if (rocket.Speed.Kmph != null) Console.WriteLine("Speed (kmph): " + rocket.Speed.Kmph); else Console.WriteLine("Unknown speed units"); Console.WriteLine("Payload: " + string.Join(",", rocket.Payload.ToArray())); /* output: Name: Falcon Message: Hi there Speed (mph):18000 Payload: GPS unit,Car */
// encode/decode useful/native C# types: decimal/double, List, Dictionary var real64 = new List<double> { 3.14D, double.NegativeInfinity, double.PositiveInfinity, double.NaN }; var realDec = new List<decimal> { 3.14M, decimal.MinusOne, decimal.One, decimal.Zero, decimal.MinValue, decimal.MaxValue }; myCodec.EncoderOptions.Formatting = Oss.DCodec.JsonFormatting.None; Console.WriteLine(myCodec.Encode(real64)); Console.WriteLine(myCodec.Encode(realDec)); /* Output: [3.1400000000000001,"-INF","INF","NaN"] [3.14,-1.0,1.0,0.0,-79228162514264337593543950335.0,79228162514264337593543950335.0] */ var dict = myCodec.Decode<Dictionary<string, object >>("{'Name':'Falcon', 'Message':'Hi there', 'Speed': { 'mph':18000 }, 'Payload': [ 'car', 'GPS unit' ] }"); Console.WriteLine(myCodec.Encode(dict)); /* Output: * {"Name":"Falcon","Message":"Hi there","Speed":{"mph":18000},"Payload":["car","GPS unit"]} */
// convert DValue to/from POCO var dvRocket = new Oss.DCodec.DValueObject { {"name", "Delta"}, {"Payload", new Oss.DCodec.DValueArray { "Car", "GPS" } } }; var pocoRocket = dvRocket.ToObject<Rocket>(); dvRocket = Oss.DCodec.DValue.FromObject(pocoRocket) as Oss.DCodec.DValueObject;
// Usually the schema, the message, and the bindings match each other. E.g. a Rocket has // a Speed property which is a number, then Speed must appear in the user defined bindings, // in the schema, and in the message, or otherwise something is wrong. // However the data change/evolve all the time, sometime unexpected, which cause // "conflicts" during serialization/validation. There are different kinds of Conflicts, // and mostly during Decode, since the incoming data are more likely to deviate. // With DCodec the app can handle conflicts and recover from them (e.g. to be // "liberal on receiving") without interrupting the operation. The app just have to provide // a conflict handler, because otherwise DCodec throws an exception. // // Lets define three sets of data for a Rocket: the schema type, the binding class, and a JSON message // each slightly different from the other two and see which conflicts the app receives // during serialization and validation. // Schema // World-Schema-C DEFINITIONS EXPLICIT TAGS::= BEGIN // Rocket ::= SEQUENCE // { // name [1] UTF8String, // speed [2] INTEGER (1..100000), // weight [3] REAL, // units [7] ENUMERATED { metric, royal }, // payload [8] SEQUENCE SIZE(10) OF UTF8String OPTIONAL, // oneway [9] BOOLEAN OPTIONAL // } // END // Pre-defined bindings (POCO) public class Rocket4 { public string Name { get; set; } public string Date { get; set; } public int Speed { get; set; } public int Stages { get; set; } public U Units {get; set;} public enum U { metric, royal } public bool? Oneway { get; set; } // OPTIONAL } public static Oss.DCodec.DResponse ConflictsHandlerPrintAndContinue(Oss.DCodec.DConflict conflict) { System.Console.WriteLine(conflict); return Oss.DCodec.DResponse.Continue; }
var myCodec = new Oss.DCodec.JsonCodec(); // we'll use default conflicts/validation options // myCodec.DecoderOptions.ValidateConstraints = true; // true is default // myCodec.DecoderOptions.ConflictsValue = Oss.DCodec.DConflictsValue.Parse;
// Schema-less decoding fires a few conflicts. For each field let's see if there is a conflict: // Bindings Message // string Name 'Name':'My rocket' -- no conflict, name and type match // string Date 'Date':2018 -- no conflict, number is converted to string // int Speed 'Speed':null -- no conflict, null is converted to 0 // int Stages 'Stages':'ABC' -- BindingTypeMismatch, string cannot be converted into int // -- 'Weight':250000 -- BindingMissing, binding is missing this field // enum Units -- -- no conflict, extra fields in the bindings are not a problem // 'Name':'Rocket' -- DuplicatedKey, Name was encountered twice // -- -- -- no conflict, Payload is only in the schema, but we don't use schema yet // bool? Oneway 'Oneway':null -- no conflict, binding is nullable, hence optional string msg=@"{ 'Name' : 'My rocket' , 'Date' : 2018, 'Speed' : null, 'Stages' : 'ABC' , 'Weight' : 250000, 'Name' : 'Rocket' , 'Oneway' : null }"; myCodec.DecoderOptions.ConflictHandler = ConflictsHandlerPrintAndContinue; var rocket = myCodec.Decode<Rocket4>(msg); // a liberal handler: c => {Log(); return DResponse.Continue;} /* Output BindingTypeMismatch: Stages value=ABC BindingMissing: Weight value=250000 DuplicatedKey: Name value=Rocket */ // Schema-less encode fire no conflicts, since the message just follows the bindings. var encoding = myCodec.Encode(rocket); /* No Output, there is no conflicts */
// Using a schema bring more potential conflicts than schema-less decoding (schema key generated @ https://asn1.io/dynamic) var mySchema = new Oss.DCodec.DSchema("..."); // Bindings Schema Message // string Name name UTF8 'Name' : 'Fake rocket' -- no conflicts // string Date -- 'Date' : 2018, -- SchemaMissing (schema has no Date) but no type mismatch (number is converted) // int Speed speed INT 'Speed' : null, -- SchemaTypeMismatch, BindingTypeMismatch, null cannot be used for a required field // int Stages -- 'Stages': 'ABC', -- BindingTypeMismatch, SchemaMissing // -- weight REAL 'Weight': true, -- BindingMissing, SchemaTypeMismatch (REAL vs boolean). // enum Units units ENUM -- -- SchemaExtra, units is expected in the message // 'Name' : 'Test rocket' -- DuplicatedKey // -- payload INT OPT -- -- no conflicts, Payload is OPTIONAL // bool Oneway oneway BOOL 'Oneway':null -- no conflicts, OPTIONAL, null in the message, no conflicts // Message msg = @"{ 'Name' : 'Fake rocket', 'Date' : 2018, 'Speed' : null, 'Stages': 'ABC', 'Weight': true, 'Name' : 'Test rocket', 'Oneway': null }"; rocket = myCodec.Decode<Rocket4>(msg, mySchema.FindType("World-Schema-C.Rocket")); /* Output SchemaMissing: World-Schema-C.Rocket.Date value=2018 SchemaTypeMismatch: World-Schema-C.Rocket.speed BindingTypeMismatch: World-Schema-C.Rocket.speed BindingTypeMismatch: World-Schema-C.Rocket.Stages value=ABC SchemaMissing: World-Schema-C.Rocket.Stages value=0 SchemaTypeMismatch: World-Schema-C.Rocket.weight value=True BindingMissing: World-Schema-C.Rocket.weight value=True DuplicatedKey: World-Schema-C.Rocket.name value=Test rocket SchemaExtra: World-Schema-C.Rocket.units */ Console.WriteLine("-------------------- Conflicts of decoding dynamic type --------------------"); // for a dynamic type, the output object is created dynamically, so there is no binding conflicts var dvRocket = myCodec.Decode(msg, mySchema.FindType("World-Schema-C.Rocket")); /* SchemaMissing: World-Schema.Rocket.Date value=2018 SchemaTypeMismatch: World-Schema.Rocket.speed SchemaMissing: World-Schema.Rocket.Stages value=ABC SchemaTypeMismatch: World-Schema.Rocket.weight value=True DuplicatedKey: World-Schema.Rocket.name value=Test rocket SchemaExtra: World-Schema.Rocket.units */
// mismatched ENUM or CHOICE items also result in conflicts msg = @"{ 'Name':'Alpha','Speed' : 1, 'Units':'British'}"; // "British" is not in the schema dvRocket = myCodec.Decode(msg, mySchema.FindType("World-Schema-C.Rocket")); /* SchemaMissing: World-Schema-C.Rocket.units.British value=British SchemaExtra: World-Schema-C.Rocket.weight */ dvRocket["Units"] = "British"; // "British" is not in the schema myCodec.EncoderOptions.ConflictHandler = ConflictsHandlerPrintAndContinue; msg = myCodec.Encode(dvRocket, mySchema.FindType("World-Schema-C.Rocket")); /* SchemaExtra: World-Schema-C.Rocket.weight SchemaMissing: World-Schema-C.Rocket.units.British value=British */
// Validating and enumerating the conflicts dvRocket = new Oss.DCodec.DValueObject { { "Name", "Delta" }, { "Speed", -1 }, // invalid, must be 1..100000 { "Units", "metric" } // { "Weight", 100.0 } // invalid, is required }; var problems = myCodec.Validate(dvRocket, mySchema.FindType("World-Schema-C.Rocket")); foreach (var p in problems) Console.WriteLine(p); /* output: ConstraintViolation: World-Schema-C.Rocket.speed value=-1 SchemaExtra: World-Schema-C.Rocket.weight */
// This example shows how to do а partial decoding, e.g. when the application // needs only a few fields (out of a big message) and the rest can be ignored. // Let's consider this schema as "big", with "lots" of fields, while the app // needs only Rocket.Speed to perform its logic. Also note, that the schema // can be extended later by adding fields in Rocket.Other. // =============================================== // World-Schema DEFINITIONS EXPLICIT TAGS::= BEGIN // Rocket ::= SEQUENCE // { // name [1] UTF8String, // speed [2] INTEGER, // other [3] SEQUENCE { ... } -- extensible // } // END // =============================================== using Oss.DCodec; // Data type to decode into (bindings) public class Essentials { // not interested in // public string Name { get; set; } // public Stuff other { get; set; } // interested in public int Speed { get; set; } }
// Partial decoding - when only parts of the message are decoded var myCodec = new JsonCodec(); // Load the schema (dkey is generated with https://asn1.io/dynamic) var mySchema = new DSchema("....dkey...."); // message includes lots of stuff, but only Speed is essential for this app string msg = "{ 'Name':'Gemini', 'Speed':45000, 'Other':{'id':'x1234', 'size':20, 'Payload':true, 'Launches':[1964,1965,1966]} }"; // bindings and the message partially match the schema, so conflicts are expected (and handled). myCodec.DecoderOptions.ConflictHandler = MyHandler; var rocket = myCodec.Decode<Essentials>(msg, mySchema.FindType("World-Schema.Rocket")); if (rocket.Speed > 40000) Console.WriteLine("Orbiting..."); // Output: // New field: World-Schema.Rocket.other.id // New field: World-Schema.Rocket.other.size // New field: World-Schema.Rocket.other.Payload // New field: World-Schema.Rocket.other.Launches // Orbiting...
public DResponse MyHandler(DConflict conflict) { switch (conflict.Kind) { // Ignore fields that are not essential case DConflictKind.BindingMissing: return DResponse.Drop; // Log fields that are new (do not match the schema) case DConflictKind.SchemaMissing: System.Console.WriteLine("New field: " + conflict.Reference); return DResponse.Continue; // Break on any other kind of conflict. DOperationInterupted will be thrown. default: return DResponse.Break; } }
// This example shows how to handle messages that come from nodes that use // a newer (evolved) version of a schema. In a "classic" decoding scenario // the whole message would be rejected (as not valid). In a "dynamic" scenario // the decoder will parse and return both, the valid (matching) part and the // invalid (conflicting) part of the message. The application then handles // the conflicted part (e.g. mine for data and decides what to do about // the whole message. // // Here are three schemas side-by-side: the original, the evolved with // a new field, and another evolved with a field type change // // The original schema (used by this app) Evolved Schema (used by a remote node) Evolved yet again // ----------------------------------------- -------------------------------------- ----------------------------------------------------------- // World-Schema DEFINITIONS ::= BEGIN World-Schema DEFINITIONS ::= BEGIN World-Schema DEFINITIONS ::= BEGIN // Rocket ::= SEQUENCE Rocket ::= SEQUENCE Rocket ::= SEQUENCE // { { { // name [1] UTF8String, name [1] UTF8String, name [1] UTF8String, // payload [2] BOOLEAN payload [2] BOOLEAN, payload [2] SEQUENCE OF UTF8String, -- change in ver.3.0 // weight [3] REAL -- new in ver 2.0 weight [3] REAL // } } } // END END END
public static void Main() { // Schema evolution // schema key (generated at https://asn1.io/dynamic) var mySchema = new Oss.DCodec.DSchema(....); var myType = mySchema.FindType("World-Schema.Rocket"); string msgVer1 = "{ 'Name' : 'My rocket', 'Payload' : true}"; string msgVer2 = "{ 'Name' : 'My rocket', 'Payload' : true, 'Weight' : 10000.0 }"; string msgVer3 = "{ 'Name' : 'My rocket', 'Payload' : ['Car'], 'Weight' : 11000.0 }"; var myCodec = new Oss.DCodec.JsonCodec(); myCodec.DecoderOptions.ConflictHandler = EvolutionHandler; var myRocket = myCodec.Decode(msgVer1, myType); // Output: no conflicts ... var myRocketIsOlder = myCodec.Decode(msgVer2, myType); // Output: // SchemaMissing: World-Schema.Rocket.Weight value=10000 (identifier not found in schema type) var myRocketIsOldest = myCodec.Decode(msgVer3, myType); // Output: // SchemaTypeMismatch: World-Schema.Rocket.payload value=["Car"] (invalid format for a boolean value) // Mine for patterns: ["Car"] // SchemaMissing: World-Schema.Rocket.Weight value=11000 (identifier not found in schema type) } public static Oss.DCodec.DResponse EvolutionHandler(Oss.DCodec.DConflict conflict) { Console.WriteLine(conflict); switch (conflict.Kind) { // we allow schema evolution e.g. the following conflicts are ok case Oss.DCodec.DConflictKind.SchemaMissing: return Oss.DCodec.DResponse.Continue; case Oss.DCodec.DConflictKind.SchemaTypeMismatch: Console.WriteLine("Mine for patterns: " + (conflict.Value as Oss.DCodec.DValue).ToString()); return Oss.DCodec.DResponse.Continue; // any other conflicts are unexpected, so the message is invalid default: return Oss.DCodec.DResponse.Break; // codec will throw DOperationInterupted } }
// This example shows how to decode DER messages for which there is no schema // (or no schema for parts of the message). The application gets the no-schema // part in a form of raw values, aka TLV (Tag/Length/Value), then use some // information, e.g check types (when available), find string pattern, etc. // NOTE: field types won't be known unless explicit tags are used // ========================================================================== // World-Schema DEFINITIONS EXPLICIT TAGS ::= // BEGIN // Rocket ::= SEQUENCE // { // name [10] UTF8String(SIZE(1..16)), // message [11] UTF8String DEFAULT "Hello World" , // fuel [12] ENUMERATED { solid, liquid, gas, hybrid}, // speed [13] CHOICE { mph [20] INTEGER, kmph [21] INTEGER } OPTIONAL, // payload [14] SEQUENCE OF UTF8String // } // END // ==========================================================================
var myCodec = new Oss.DCodec.DerCodec(); // message in DER (JSON equivalent { "name":"Falcon", "message":"Hello World", "fuel":"solid", "speed":{ "mph":10000}, "payload":["Car","GPS" ] }) byte[] msgDerRawExplicitTags = new byte[] { 0x30, 0x25, 0xAA, 0x08, 0x0C, 0x06, 0x46, 0x61, 0x6C, 0x63, 0x6F, 0x6E, 0xAC, 0x03, 0x0A, 0x01, 0x00, 0xAD, 0x06, 0xB4, 0x04, 0x02, 0x02, 0x27, 0x10, 0xAE, 0x0C, 0x30, 0x0A, 0x0C, 0x03, 0x43, 0x61, 0x72, 0x0C, 0x03, 0x47, 0x50, 0x53}; var stream = new System.IO.MemoryStream(msgDerRawExplicitTags); var tlvs = myCodec.Decode(stream); Console.WriteLine("Explore/iterate:"); foreach (var elem in tlvs.Iterator.Descendants) Console.WriteLine($" {elem.Indent}{elem.Index:X}:[{elem.Identifier}] {elem.Value.DataType}, {elem.Value.Data}"); Console.WriteLine("Access by index:"); Console.WriteLine($" name = {tlvs[0]}"); // ordered object (SEQUENCE) Console.WriteLine($" speed.mph = {tlvs[2][0]}"); // nested (SEQUENCE or CHOICE) Console.WriteLine($" payload[0] = {tlvs[3][0]}");// array (SEQUENCE OF) Console.WriteLine("Access by tag:"); Console.WriteLine($" name = {tlvs["10"]}"); Console.WriteLine($" speed.mph {tlvs["13"]["20"]}"); Console.WriteLine($" payload[0] = {tlvs["14"][0]}"); if (!tlvs.ContainsKey("42")) // doesn't exist Console.WriteLine(" Field not found"); /* output: Explore/iterate: 0:[10] String, Falcon 1:[12] Int, 0 2:[13] Raw, 0:[20] Int, 10000 3:[14] Raw, 0:[0] String, Car 1:[1] String, GPS Access by index: name = Falcon speed.mph = 10000 payload[0] = Car Access by tag: name = Falcon speed.mph 10000 payload[0] = Car Field not found */
// This example shows DER encoding/decoding. // DCodec is strict on encoding (the encoding must be valid) // and is liberal on decoding (allows partial schema match). // // Use of EXPLICIT TAGS mode ensures that DER tags will include for both, // field identification and type, which allows greater flexibility // (e.g. schema evolution, partial decoding, making sense of fields // that do not exists in the schema. // // MySchema DEFINITIONS EXPLICIT TAGS::= BEGIN // Rocket ::= SEQUENCE // { // name [1] UTF8String (SIZE (4..16)) DEFAULT "Falcon", // speed [2] INTEGER (0..1000), // weight [3] REAL (0..1000) OPTIONAL, // units [4] ENUMERATED { metric, imperial } // } // END using Oss.DCodec;
// Encode and Decode DER var myCodec = new DerCodec(); var jsonCodec = new JsonCodec(); // for debugging in JSON // schema types var mySchema = new DSchema("Zb5qQllrUFRhNbCmGA22SiHkvVOZhXLQoJYwCNrHkvKylA9JgN8oiFX4xNgnlvkTFEGhL4tUfFZxdTT2Dy9Vorrko8pSuniCZQUP416wH8lQAPAu+1rz+Zdxrnzp3GHPaHYmqyWXHPAO3XhPrYHJl4B6b9Hanz+2g3kSyjtJuxNd9Qy+fjx4/5uhXo4EJ2gVGd4MM0XypcNKEkV34BhDE7oDtOXfmtM1sJWf5RjJoxVT8LxR+E8N5sUKPwspESsZiSAKmEX8AB/RD0IBNXYi/fQ9ZPZvCVl87Mpi1GYVSZE667Ru/ds1RpLD0KGgpfpBGQSLGMSJrtz5RmCQg8KFvYOrE7xOxyhjZGXuN3JN4nUtZPB/cCDcbfB33c0enKrpgFPuRKch0zxuAVIvR52RdIw3WvcLdKtQh5AUg41cM1ThleklHkuX/apJ8XZLCgh/F+HpGWC5X7KN1BuYRiWR8E89wBGM/7dww9UKKlptgvbdGkhIVH23ZXDvDhZhXWfYucsyIY1pMp9F5Huoq7UH2SN79xWB+IgnIqTyBthukW6qEnDLcMzI0EV/A3vO3shXO27IJjNgRmTtooNq7VZVhabYPrLp+SucBA+DEyr3gxx7SWvRm68lbF0zCsoGnlu0lqT8OpbQDLAU6eErMkmhjDOvLVoY60Ua7l7NuHVADWugsCc7uTskkPEHzQuLKi6ZionfVPV61I6hCloiT9Z4kyzRRQwCNj3OCOKZYJyqmbifjk6bN9XPumnABZ3vQWmoym41GN8kFRWfBHW9xgmgOrKrVohLLI9BmXeGS2ceDRbRS0fl8t5oioB0IDtPddPIFcjtTBv++Nf6PK4T++iI6DISbz/R4lC7MbodrNzH4n8wedUn2eNW7IHuHhjyWgQdKvSqRtHYQDG4W/eFH/NSUw0WTiea9R4vy7VukAFMH5xoM7OWGsxmUFQ="); var myType = mySchema.FindType("MySchema.Rocket"); var rocketOut = new DValueObject { //["name"] = "Falcon", // default value, no need to specify/encode ["speed"] = 0, ["weight"] = 100.5M, ["units"] = "metric", ["payload"] = "mirror" // extra field, not in the schema, won't be encoded }; var hasConflicts = false; myCodec.EncoderOptions.ConflictHandler = c => { hasConflicts = true; Console.WriteLine(c); // log the conflicts return DResponse.Drop; // Drop extra fields }; var derRocket = myCodec.Encode(rocketOut, myType); if (hasConflicts) Console.WriteLine("Encoding encountered some conflicts..."); /* Output: SchemaMissing: MySchema.Rocket.payload (identifier not found in schema type) Encoding encountered some conflicts... */ // print DER Console.WriteLine("HEX: " + BitConverter.ToString(derRocket)); Console.WriteLine("JSON: " + jsonCodec.Encode(myCodec.Decode(derRocket, myType))); /* Output: * HEX: 30-17-A2-03-02-01-00-A3-0B-09-09-03-31-30-30-35-2E-45-2D-31-A4-03-0A-01-00 * JSON: {"speed":0,"weight":100.5,"units":"metric","name":"Falcon"} */ // a message that contains an extra field byte[] derRocketExtra = new byte[] { 0x30, 0x21, 0xA2, 0x03, 0x02, 0x01, 0x00, 0xA3, 0x0B, 0x09, 0x09, 0x03, 0x31, 0x30, 0x30, 0x35, 0x2E, 0x45, 0x2D, 0x31, 0xA4, 0x03, 0x0A, 0x01, 0x00, 0xA5, 0x08, 0x0C, 0x06, 0x6D, 0x69, 0x72, 0x72, 0x6F, 0x72 }; myCodec.DecoderOptions.ConflictHandler = c => { Console.WriteLine(c); // log the conflicts return DResponse.Continue; // allow extra fields }; var rocketIn = myCodec.Decode(derRocketExtra, myType); Console.WriteLine("JSON: " + jsonCodec.Encode(rocketIn)); /* Output: SchemaMissing: MySchema.Rocket.[5] value=mirror (a field with DER tag [5] was not found in schema type) JSON: {"speed":0,"weight":100.5,"units":"metric","5":"mirror","name":"Falcon"} */