I have released a small C# library called LinqToJSON.
What is LinqToJSON?
LinqToJSON is a .NET assembly, written in C#, that allows reading and writing data formatted according to the JSON standard with features that mimic those of LinqToXML. It is available for free, with its source code.
The assembly name is Ranslant.Linq.Json.
Documentation and source code
The full documentation has been posted as a CodeProject article, and you can find the source code there as well.
Read it all and do not hesitate to rate my work.
What does Ranslant.Linq.Json bring that other JSON writers/readers do not have?
- It does not use a serializer at all
- this ensures that the files written and read conforms very strictly to the standard
- This ensures that the files written can be read anywhere else (you often need to serialize/deserialize with the same serializer, because not all share the same conventions)
- It provides a set of classes that work in a way very similar to LinqToXML
- If you already worked with LinqToXML you can work with LinqToJSON
- It is very easy to read JSON data thanks to the querying power of LINQ
- It is very easy to write JSON data (check the article on CodeProject for examples)
- The data you give or you get is validated for correctness
- No errors codes, only thrown Exceptions
- The source code is provided, and made easy to read and understand
- The assembly is very small (14 KB)
Excerpts from the article follow.
Specifications (what I wanted)
Public objects
Similar to XDocument
, XElement
and XAttribute
from LinqToXml, I want to have:
JDocument
, as the root object. Derived class ofJObject
, to allow directly using the properties and methods ofJObject
.- Generic containers
JObject
andJArray
hold values. They implement the interfaceIJValue
and provide accessors to the JSON content.- Specific containers
JString
– I do not want to use the built-in typeSystem.String
directly, because conceptually a JSON string and a C# string are two different items.JString
implements the interfaceIJValue
and checks the validity of the given C# string, according to the JSON specifications.JNumber
– Same story here as to why I do not use the typeDouble
directly. This class implements the interfaceIJValue
and checks the validity of the given C# string, regarding the JSON specifications for numbers.JTrue
,JFalse
andJNull
– They also implement the interfaceIJValue
. Again, I did not want to use the typesbool
andobject
directly.
Reading JSON
- Provide a static method
JDocument.Load()
Accessors will return
IEnumerable<IJvalue>
when the calling object is aJObject
orJarray
, to allow for Linq-style querying- a value of the appropriate type and content for the containers: ‘bool’ for
JTrue
andJFalse
, ‘double’ forJNumber
, ‘string’ forJString
and ‘object’ forJNull
. - The generic type is
IJValue
. Filtering the values returned can be done over the type, using the keyword is.
code snippet
// first, load a file JDocument jDoc = JDocument.Load(filename); // then, you can use LINQ to parse the document var members = from member in jDoc.GetMembers() select member; foreach (var member in members) { // you can filter the values using their type if (member.Value is JString || member.Value is JNumber) // do something ... // you can filter the values using their type if (member.Value is JObject) { JObject obj = member.Value as JObject; var names = from name in obj.GetNames() select name; foreach (var name in names) { // you can get an object with its name IJValue value = obj[name]; if (value is JFalse || value is JTrue || value is JNull) // do something ... } var members = from member in obj.GetMembers() select member; foreach (var member in members) { Console.WriteLine("\t{0} : {1}", member.Name, member.Value.ToString()); } } }
Writing JSON
- Create a new
JDocument
instance - Add content in several ways.
- either in the constructors of the JSON values directly
- or with the family of methods
Add()
Each class implementing the interface
IJValue
provides a method returning a string that contains the correct representation. For most object I do not use ToString() because I want to keep the JSON string representation of this object independent of C#.- Provide a
JDocument
instance methodSave()
See the classes’ description below and the code of the demo application for more details.
code snippet #1
// example of JDocument written only through the constructor: new JDocument( new JObjectMember("D'oh", new JObject( new JObjectMember("First Name", new JString("Homer")), new JObjectMember("Family Name", new JString("Simpson")), new JObjectMember("Is yellow?", new JTrue()), new JObjectMember("Children", new JArray( new JString("Bart"), new JString("Lisa"), new JString("Maggie")) ) ) ), new JObjectMember("never gets older", new JObject( new JObjectMember("First Name", new JString("Bart")), new JObjectMember("Family Name", new JString("Simpson")) ) ) ).Save("simpsons.json");
There are several interesting aspects in the example above:
- except the final call to Save() all the data is created through constructors
- the code can be presented in a very visual way, that mimics the structure of the JSON data
- This JSON data can most likely not be deserialized, mostly because of the non-alphanumerical characters in the names of the object members
code snippet #2
// you can create an empty object and add values JObject obj = new JObject(); obj.Add("_number", new JNumber(-3.14)); obj.Add("_true", new JTrue()); // notice the use of JTrue obj.Add("_null", new JNull()); // notice the use of JNull // you can create an empty array and add values JArray arr = new JArray(); // ... either only one value arr.Add(new JNumber("-15.64")); // ... or more than one at once // Notice that prefixing your strings with @ will help keeping them as valid JSON strings arr.Add(new JString(@"Unicode: \u12A0"), new JString(@"\n\\test\""me:{}")); JDocument doc = new JDocument( new JObjectMember("_false", new JFalse()), new JObjectMember("_false2", new JFalse()) ); // the same name cannot be used twice! // Add() has two forms: // 1. with JObjectMember, you can give one or more doc.Add( new JObjectMember("_array", arr), new JObjectMember("_string1", new JString("string1")), ); // 2. directly give the name and the value doc.Add("_obj", obj); doc.Save(filename);
Points of Interest
Source code
I have tried to write code that is easy to understand yet efficient enough. I have avoided complex constructs and design patterns, because reading/writing JSON data is, in itself, not a complex task and therefore should not require complex code.
A few things are worth mentioning for the beginners in C#:
- Use of regular expressions (in
JNumber
for instance) - Lazy creation of the data structures in
JObject
andJArray
(Add()
).The private data containers are initialized only once they are actually needed. - Use of an extension method (
ConsumeToken()
) - Use of an interface as generic base type (
IJValue
) , in combination with a simple factory pattern (JParser.ParseValue()
) - Methods with variable number of arguments (e.g. Add
(params JObjectMember[] jObjectMembers
)
) - Use of
IEnumerable<T>
to benefit from Linq’s querying power
About the parser
There are hundreds of ways to write a text parser. It is my opinion that all of them are right as long as the text is properly parsed and you get the specified (and hopefully expected) result. Which means: my parser is good because it works. This is all I expect from it.
What I do here is simple. I parse the text looking for tokens. Those tokens represent delimiters for specific JSON value types. In other words a token or a set of tokens give away the expected value type. The text between those tokens is then checked for validity, which means I check if what I have actually represents the value type that I expect from the delimiters.
What the parser does is follow the grammatical rules of JSON and report an error (by throwing an exception) when a rule is violated.
The way the string is technically parsed is by eating it up, so to speak, until it is empty. In other words, what is checked (whether tokens or content) is removed from the string to be parsed. Picture Pac-Man eating the pills in the maze, one pill at a time. Well, same thing here. 🙂
To achieve this I have chosen the following code structure:
- A class field containing the string that will be consumed. This field is not static since I want to stay thread safe. Each parsing step will modify this string, by removing the current token or content.
- A set of parsing function, each one devoted to recognizing and/or filling one and only one type of value. Which one is called depends on the token found.
Converting a base class to one of its derived classes
The only specific difficulty of the parser was getting ParseDocument(string)
to return a JDocument
instance although it was actually parsing a JObject
.
First I had the following code:
JDocument jDoc = (JDocument)ParseObject();
It compiled, but threw an InvalidCastException
(see tthis MSDN article and search for ‘Giraffe’). Indeed there is no equivalent to the C++ dynamic_cast in C#, and therefore you cannot convert a base class into one of its derived classes.
There are a few solutions for this issue:
- SELECTED SOLUTION – Copy the members of the object returned by
ParseObject()
into the base of theJDocument
object (using the constructorJDocument(JObject jObject)
). This works well and requires on-ly very little code. Besides, this solution allows to generally create a JDocument out of any JObject, which can also be useful. - Encapsulate a JObject instance in JDocument. I found this to be not as nice, in terms of usability, as the first solution.
- use the
CopyTo()
method ofJObject
. This uses reflection (and can therefore bring per-formance issues) and is not guaranteed to work according to this post on MS Forums) - derive
JDocument
fromIJValue
. But then I loose inheriting theJObject
methods and I would have needed to duplicate them inJDocument
. This is definitely not clean.
I found the solution #1 to be the only clean and elegant solution, but it works only because in JObject
the fields and properties (there’s actually only one field) are accessed for writing only through public accessor functions (the Add()
functions) that can be used freely in JDocument
thanks to the inheritance.
This means that this pattern cannot be used generically.
Read the source code and the comments there for more information.
— End of excerpts
This was a fun little project, and even THE Sascha Barber wrote that my work is great! 😀
Say what you want, I’m proud.
Now I have to find a subject matter for the next article…