Create data using object oriented blocks, DTO-free.
With ZiZZi, you can create the declaration of data before deciding about its format. You can then transform the declaration to a property object as well as xml, json, or whatever you need.
ZiZZi is different from common serialization principles. It is built as a "printer", not a transformer. It does not take some input data format/which is then transformed - instead, it just funnels the content directly from the sources. This can lead to less complex code and offer interesting advantages, like near-costless data masking.
The code follows all the principles suggested in the two "Elegant Objects" books. It adopts the approach of the blog article printers instead of getters.
This is a fork of BriX. This library offers more flexibility over the resulting output.
-
BriX' main goal was to express data in a way that fits json as well as xml. It achieved this by offering a limited set of structuring features: For example, json does not have attributes, BriX does not support them. ZiZZi lets this choice open to the library user, as the interfaces can be used to introduce more types.
-
With ZiZZi you can transform to anonymous objects - only compiling the necessary properties - which can be handy for UI building.
-
ZiZZi can express typed content, also raw bytes and streams - BriX is limited to strings.
var menu =
new ZiBlock("menu",
new ZiBlockArray("meals", "meal",
new ZiBlock(
new ZiProp("name", "Pizza Funghi"),
new ZiProp("price", 9.50)
),
new ZiBlock(
new ZiProp("name", "Burger Helene with Fritten"),
new ZiProp("price", 10.50)
)
),
new ZiBlockArray("drinks", "drink",
new ZiBlock(
new ZiProp("name", "Beer"),
new ZiProp("price", 2.50)
),
new ZiBlock(
new ZiProp("name", "Beer"),
new ZiProp("price", 35.00)
)
)
);
var obj =
new ZiBlock(
new ZiProp("Name", "Mr.Object"),
new ZiProp("Information", () => ComplexInformation())
).Form(
ObjectMatter.Fill(new { Name = "" })
);
//will give you an anonymous object with the properties filled by the defined ZiZZi.
//"Information" is not requested, so the ComplexInformation method is not being executed.
Assert.Equal("Mr.Object", obj.Name);
);
Console.WriteLine(
menu.Form(new XmlMatter())
);
Will give you
<menu>
<meals>
<meal>
<Name>Pizza Funghi</Name>
<Price>9.50</Price>
</meal>
<meal>
<Name>Burger Helene with Fritten</Name>
<Price>10.50</Price>
</meal>
</meals>
<Drinks>
<Drink>
<Name>Beer</Name>
<Price>2.50</Price>
</Drink>
<Drink>
<Name>Beer</Name>
<Price>35.00€</Price>
</Drink>
</Drinks>
</menu>
Console.WriteLine(
menu.Form(new JsonMatter())
);
Will give you
{
"meals": [
{
"Name": "Pizza Funghi",
"Price": 9.50
},
{
"Name": "Burger Helene with Fritten",
"Price": 10.50
}
],
"Drinks": [
{
"Name": "Beer",
"Price": 2.50
},
{
"Name": "Beer",
"Price": 35.00
}
]
}
You can build Blox which will aggregate data only when printed:
var report =
new ZiBlock("Weather Report",
new ZiProp("Temperature", () => weatherServer.Degrees("Berlin").AsDouble()) //not yet read
);
report.Form(new XmlMatter()); //Temperature is read while printing
With ZiZZi you can build data structures without deciding which format someone should use to work with it. This has advantages for example when designing a web-API: When you add a new usecase, you design the output using ZiZZi objects and can leave the decision if the user needs xml or json to the http request header.
var report =
new ZiBlock("report",
new ZiProp("All good", "Yes")
);
if(new Header.Of("accept", httpRequest) == "application/xml")
{
return myReport.Form(new XmlMatter()).ToString();
}
else if(new Header.Of("accept", httpRequest) == "application/json")
{
return myReport.Form(new JsonMatter()).ToString();
}
In other scenarios you may deliver a non printed ZiZZi block as payload of another object, and if the user just needs the head of your object, but not the payload, computation time can be saved.
//Someone sends this:
public void Send()
{
var signal =
new SignalOf("Event", "Something is on fire",
new ZiArray("Burning things"
new ListOf<string>(
() => BurningThings()
)
)
);
}
public void BurningThings()
{
var burning = new List<string>();
if(inventory.Cat().IsBurning())
{
burning.Add("The cat");
}
}
//...control flow happens in between...
//Someone receives:
var received = signal;
if(signal.Prop("event" == "Something exploded")) //is false in this example
{
var payload =
signal.Payload()
.Form(new XmlMatter()); //Only when you print, the burning things would be inspected.
}
ZiZZi can form XML and Json, and more media formats can be added by implementing the IMatter interface. Because XML and Json have different feature sets, ZiZZi's offered objects use only these features to ensure Blox are compatible to both xml and json. However, the IMatter interface can be implemented to support more features of a speccific format.
Every Block must have a name:
//Xml
<root>
<branch>My Branch</branch>
</root>
//Json
{
"branch": "My branch"
}
//C#
var blox =
new ZiBlock(
"root", //name must be specified.
new ZiProp("branch", "My Branch")
);
Obviously, the name "root" is lost when printing to Json. But ZiZZi encourages that you specify it, because it is needed for Xml.
The same is the case for lists:
//Xml
<shopping-list>
<fruits>
<fruit>Apple</fruit>
<fruit>Banana</fruit>
</fruits>
</shopping-list>
//Json
{
"fruits":
[
"Apple",
"Banana"
]
}
//C#
var list =
new ZiBlock("shopping-list",
new ZiValueList("fruits", "fruit", //"fruit" as name for entries must be specified
"Apple",
"Banana"
)
);
And for block lists:
//Xml
<shopping-list>
<fruits>
<fruit>
<name>Apple</name>
<weight>500</weight>
</fruit>
<fruit>
<name>Banana</name>
<weight>300</weight>
</fruit>
</fruits>
</shopping-list>
//Json
{
"fruits": [
{
"name": "Apple",
"weight": "500"
},
{
"name": "Banana",
"weight": "300"
}
]
}
//C#
var list =
new ZiBlock("shopping-list",
new ZiBlockArray("fruits", "fruit", //"fruit" as name for entries must be specified
new ZiBlock(
new ZiProp("name", "Apple"),
new ZiProp("weight", "500")
),
new ZiBlock(
new ZiProp("name", "Banana"),
new ZiProp("weight", "300")
)
)
);
Json does not support attributes, so they are not included in Blox objects. If you need them, you might implement or extend the JsonMatter objects to support them and write Blox objects.
Easy way for adding mutiple Props by KeyValuePairs. Note: no duplicated keys are allowed!
//C#
new ZiBlock(
"person", //this key is visible in xml, but not in json or result objects.
new ZiMap(
"name", "George",
"gender", "male"
)
);
//xml
<person>
<name>George</name>
<gender>male</gender>
</person>
//json
{
"name": "George",
"gender": "male"
}
//object
new { Name: "George", Gender: "Male" }
Instead of using large if/else constructs:
new ZiBlock("Todos",
new ZiConditional(() => Now.IsDay(),
new ZiProp("Todo", "Daylight dependent tasks")
),
new ZiConditional(() => Now.IsNight(),
new ZiProp("Todo", "Nightly tasks")
)
)