Click here to Skip to main content
15,881,027 members
Articles / Programming Languages / XSLT

JUST - JSON Under Simple Transformation

Rate me:
Please Sign up or sign in to vote.
4.85/5 (44 votes)
11 Jun 2018CPOL7 min read 109.8K   51   80
JSON to JSON transformation - XSLT equivalent for JSON

Introduction

This article describes how to use the JUST.NET library to transform JSON documents.

Background

JUST stands for JSON Under Simple Transformation. XSLT is a very popular way of transforming XML documents using a simple transformation language.

More and more applications are now using JSON as a data format because it is much simpler and less bulkier than XML.

However, there isn't a very easy way to transforming JSON documents.

I have created a library in .NET which enables the transformation of JSON documents using very a simple transformation language. This is an attempt to create an XSLT parallel for JSON.

This article describes how to use that library.

Install the Nuget Package

Pull the latest JUST.NET from https://www.nuget.org.

JavaScript
Install-Package JUST

A dotnetcore version is also available:

JavaScript
Install-Package JUST.NETCore

A .NET standard version is also available. This is the version which will be supported from now on.

JavaScript
Install-Package JUST.NET

Using the Code

Once you download the Nuget package, you create a simple console application.
Below is a simple C# code snippet that you can use to transform your JSON:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using JUST;
using System.IO;

namespace JUST.Test
{
    public class Program
    {
        public static void Main(string[] args)
        {
            string input = File.ReadAllText("Input.json");

            string transformer = File.ReadAllText("Transformer.json"); ;

            string transformedString = JsonTransformer.Transform(transformer, input);

            Console.WriteLine(transformedString);           
        }
    }
}

Here, Input.json is the input JSON document while Transformer.json is the JSON document that transforms the input JSON.

Using JUST to Transform JSON

JUST is a transformation language just like XSLT. It includes functions which are used inside the transformer JSON to transform the input JSON in a desired output JSON. This section describes the various functions present in JUST and how they can be used to transform your JSON.

Every JUST function starts with "#" character.

valueof

This function is used to extract the value of a given property. The value is extracted using JSON path of the property. For more information on how to use JSON path, refer to:

Consider the input:

JavaScript
{
  "menu": {  
    "popup": {
      "menuitem": [
       {
          "value": "Open",
          "onclick": "OpenDoc()"
        },
        {
          "value": "Close",
          "onclick": "CloseDoc()"
        }
      ]
    }
  }
}

Transformer:

JavaScript
{
  "result": {
    "Open": "#valueof($.menu.popup.menuitem[?(@.value=='Open')].onclick)",
    "Close": "#valueof($.menu.popup.menuitem[?(@.value=='Close')].onclick)"
  }
}

Output:

JavaScript
{
   "result":{"Open":"OpenDoc()","Close":"CloseDoc()"}
}

ifcondition

This condition is used to evaluate an if-else condition.

JavaScript
ifcondition(condition expresson, evaluation expression, true result, false result)

All four of the parameters can be a 'valueof' expressions or constants.

Consider the input:

JavaScript
{
  "menu": {
    "id" : "github",
    "repository" : "JUST"
  }
}

Transformer:

JavaScript
{
  "ifconditiontesttrue": "#ifcondition
  (#valueof($.menu.id),github,#valueof($.menu.repository),fail)",
  "ifconditiontestfalse": "#ifcondition
  (#valueof($.menu.id),xml,#valueof($.menu.repository),fail)"
}

Output:

JavaScript
{
   "ifconditiontesttrue":"JUST",
   "ifconditiontestfalse":"fail"
}

string and math Functions

At the moment, only the basic and often used string and math functions are provided in the library.

  • lastindexof(input string,search string)
  • firstindexof(input string,search string)
  • substring(input string,start index,length)
  • concat(string 1,string 2)
  • add(value 1,value 2)
  • subtract(value 1,value 2)
  • multiply(value 1,value 2)
  • divide(value 1,values 2)

Consider the input:

JavaScript
{
  "stringref": "thisisandveryunuasualandlongstring",
  "numbers": [ "1", "2", "3", "4", "5" ]
}

Transformer:

JavaScript
{
  "stringresult": {
    "lastindexofand": "#lastindexof(#valueof($.stringref),and)",
    "firstindexofand": "#firstindexof(#valueof($.stringref),and)",
    "substring": "#substring(#valueof($.stringref),9,11)",
    "concat": "#concat(#valueof($.menu.id.file),#valueof($.menu.value.Window))"
  },
  "mathresult": {
    "add": "#add(#valueof($.numbers[0]),3)",
    "subtract": "#subtract(#valueof($.numbers[4]),#valueof($.numbers[0]))",
    "multiply": "#multiply(2,#valueof($.numbers[2]))",
    "divide": "#divide(9,3)"
  }
}

Output:

JavaScript
{"stringresult":
   {
    "lastindexofand":"21",
    "firstindexofand":"6",
    "substring":"veryunuasua",
    "concat":""
   },
   "mathresult":
   {
    "add":"4",
    "subtract":"4",
    "multiply":"6",
    "devide":"3"
   }
}

Opearators

The following operators have been added to compare strings and numbers:

  • stringequals(string1, string2)
  • stringcontains(string1, string2)
  • mathequals(decimal1, decimal2)
  • mathgreaterthan(decimal1, decimal2)
  • mathlessthan(decimal1, decimal2)
  • mathgreaterthanorequalto(decimal1, decimal2)
  • mathlessthanorequalto(decimal1, decimal2)

Consider the input:

JavaScript
{
  "d": [ "one", "two", "three" ],
  "numbers": [ "1", "2", "3", "4", "5" ]
}

Transformer:

JavaScript
{
  "mathresult": {
    "third_element_equals_3": "#ifcondition(#mathequals(#valueof($.numbers[2]),3),true,yes,no)",
    "third_element_greaterthan_2": 
          "#ifcondition(#mathgreaterthan(#valueof($.numbers[2]),2),true,yes,no)",
    "third_element_lessthan_4": 
          "#ifcondition(#mathlessthan(#valueof($.numbers[2]),4),true,yes,no)",
    "third_element_greaterthanorequals_4": 
          "#ifcondition(#mathgreaterthanorequalto(#valueof($.numbers[2]),4),true,yes,no)",
    "third_element_lessthanoreuals_2": 
          "#ifcondition(#mathlessthanorequalto(#valueof($.numbers[2]),2),true,yes,no)",
    "one_stringequals": "#ifcondition(#stringequals(#valueof($.d[0]),one),true,yes,no)",
    "one_stringcontains": "#ifcondition(#stringcontains(#valueof($.d[0]),n),true,yes,no)"
  }
}

Output:

JavaScript
{"mathresult":   {"third_element_equals_3":"yes","third_element_greaterthan_2":"yes",
   "third_element_lessthan_4":"yes","third_element_greaterthanorequals_4":"no",
   "third_element_lessthanoreuals_2":"no","one_stringequals":"yes","one_stringcontains":"yes"}}

Aggregate Functions

The following aggregate functions are provided for single dimensional arrays:

  • concatall(array)
  • sum(array)
  • average(array)
  • min(array)
  • max(array)
JavaScript
Consider the input:-
{
  "d": [ "one", "two", "three" ],
  "numbers": [ "1", "2", "3", "4", "5" ]
}

Transformer:

JavaScript
{
  "conacted": "#concatall(#valueof($.d))",
  "sum": "#sum(#valueof($.numbers))",
  "avg": "#average(#valueof($.numbers))",
  "min": "#min(#valueof($.numbers))",
  "max": "#max(#valueof($.numbers))"
}

Output:

JavaScript
{
    "conacted":"onetwothree",
    "sum":"15",
    "avg":"3",
    "min":"1",
    "max":"5"
}

Aggregate Functions for Multidimensional Arrays

These functions are essentially the same as the above ones, the only difference being that you can also provide a path to point to particluar element inside the array:

  • concatallatpath(array,path)
  • sumatpath(array,path)
  • averageatpath(array,path)
  • minatpath(array,path)
  • maxatpath(array,path)

Consider the input:

JavaScript
{
   "x": [
    {
      "v": {
        "a": "a1,a2,a3",
        "b": "1",
        "c": "10"
      }
    },
    {
      "v": {
        "a": "b1,b2",
        "b": "2",
        "c": "20"
      }
    },
    {
      "v": {
        "a": "c1,c2,c3",
        "b": "3",
        "c": "30"
      }
    }
  ]
}

Transformer:

JavaScript
{
  "arrayconacted": "#concatallatpath(#valueof($.x),$.v.a)",
  "arraysum": "#sumatpath(#valueof($.x),$.v.c)",
  "arrayavg": "#averageatpath(#valueof($.x),$.v.c)",
  "arraymin": "#minatpath(#valueof($.x),$.v.b)",
  "arraymax": "#maxatpath(#valueof($.x),$.v.b)"
}

Output:

JavaScript
{
    "arrayconacted":"a1,a2,a3b1,b2c1,c2,c3",
    "arraysum":"60",
    "arrayavg":"20",
    "arraymin":"1",
    "arraymax":"3"
}

Bulk Functions

All the above functions set property values to predefined properties in the output JSON. However, in some cases, we don't know what our output will look like as it depends on the input. Bulk functions are provided for this purpose. They correspond with the template-match functions in XSLT.

Bulk functions by law have to be the first property of the JSON object. All bulk functions are represented as array elements of the property '#'.

These are the bulk functions provided as of now:

  • copy(path)
  • replace(path)
  • delete(path)

Consider the input:

JavaScript
{
  "tree": {
    "branch": {
      "leaf": "green",
      "flower": "red",
      "bird": "crow",
      "extra": { "twig":"birdnest" }
    },
    "ladder": {"wood": "treehouse" }
  }
}

Transformer:

JavaScript
{
  "#": [ "#copy($)",  "#delete($.tree.branch.bird)", 
         "#replace($.tree.branch.extra,#valueof($.tree.ladder))" ],
  "othervalue" : "othervalue"
}

Output:

JavaScript
{
   "othervalue":"othervalue",
   "tree":{
    "branch":{
     "leaf":"green",
     "flower":"red",
     "extra":{
      "wood":"treehouse"
     }
    },
    "ladder":{
     "wood":"treehouse"
    }
  }
}

Array Looping

In some cases, we don't want to copy the entire array to the destination JSON. We might want to transform the array into a different format, or have some special logic for each element while setting the destination JSON.
For these cases, we would use array looping.

These are the functions provided for this pupose:

  • loop(path) - path is the path of the array to loop
  • currentvalue()
  • currentvalueatpath(path) - here path denotes the path inside the array
  • lastvalueatpath(path) - here path denotes the path inside the array
  • currentindex()
  • lastindex()
  • lastvalue()

Cosider the input:

JavaScript
{
  "tree": {
    "branch": {
      "leaf": "green",
      "flower": "red",
      "bird": "crow",
      "extra": { "twig": "birdnest" }
    },
    "ladder": { "wood": "treehouse" }
  },
  "numbers": [ "1", "2", "3", "4" ],
  "arrayobjects": [
    {"country": {"name": "norway","language": "norsk"}},
    {
      "country": {
        "name": "UK",
        "language": "english"
      }
    },
    {
      "country": {
        "name": "Sweden",
        "language": "swedish"
      }
    }]
}

Transformer:

JavaScript
{
  "iteration": {
    "#loop($.numbers)": {
      "CurrentValue": "#currentvalue()",
      "CurrentIndex": "#currentindex()",
      "IsLast": "#ifcondition(#currentindex(),#lastindex(),yes,no)",
      "LastValue": "#lastvalue()"
    }
  },
  "iteration2": {
    "#loop($.arrayobjects)": {
      "CurrentValue": "#currentvalueatpath($.country.name)",
      "CurrentIndex": "#currentindex()",
      "IsLast": "#ifcondition(#currentindex(),#lastindex(),yes,no)",
      "LastValue": "#lastvalueatpath($.country.language)"
    }
  },
  "othervalue": "othervalue"
}

Output:

JavaScript
{

"iteration":[
   {"CurrentValue":"1","CurrentIndex":"0",
   "IsLast":"no","LastValue":"4"},
   {"CurrentValue":"2","CurrentIndex":"1",
   "IsLast":"no","LastValue":"4"},
   {"CurrentValue":"3","CurrentIndex":"2",
   "IsLast":"no","LastValue":"4"},
   {"CurrentValue":"4","CurrentIndex":"3",
   "IsLast":"yes","LastValue":"4"}
  ],
   "iteration2":[
   {"CurrentValue":"norway","CurrentIndex":"0",
   "IsLast":"no","LastValue":"swedish"},
   {"CurrentValue":"UK","CurrentIndex":"1",
   "IsLast":"no","LastValue":"swedish"},
   {"CurrentValue":"Sweden","CurrentIndex":"2",
   "IsLast":"yes","LastValue":"swedish"}
  ],
"othervalue":"othervalue"
}

Nested Array Looping (Looping Within Context)

A new function loopwithincontext has been introduced to be able to loop withing the context of an outer loop.

Cosider the input:

JavaScript
{
  "NestedLoop": {
    "Organization": {
      "Employee": [
        {
          "Name": "E2",
          "Details": [
            {
              "Country": "Iceland",
              "Age": "30",
              "Name": "Sven",
              "Language": "Icelandic"
            }
          ]
        },
        {
          "Name": "E1",
          "Details": [
            {
              "Country": "Denmark",
              "Age": "30",
              "Name": "Svein",
              "Language": "Danish"
            }
          ]
        }
      ]
    }
  }
}

Transformer:

JavaScript
{
  "hello": {
    "#loop($.NestedLoop.Organization.Employee)": {
      "CurrentName": "#currentvalueatpath($.Name)",
      "Details": {
        "#loopwithincontext($.Details)": {
          "CurrentCountry": "#currentvalueatpath($.Country)"
        }
      }
    }
  }
}

Output:

JavaScript
{ 
   "hello":[ 
      { 
         "CurrentName":"E2",
         "Details":[ 
            { 
               "CurrentCountry":"Iceland"
            }
         ]
      },
      { 
         "CurrentName":"E1",
         "Details":[ 
            { 
               "CurrentCountry":"Denmark"
            }
         ]
      }
   ]
}

Array Grouping

A function similar to SQL GROUP BY clause has been introduced to group an array based on the value of an element.

JavaScript
grouparrayby(path,groupingElementName,groupedElementName)

Input:

JavaScript
{
  "Forest": [
    {
      "type": "Mammal",
      "qty": 1,
      "name": "Hippo"
    },
    {
      "type": "Bird",
      "qty": 2,
      "name": "Sparrow"
    },
    {
      "type": "Amphibian",
      "qty": 300,
      "name": "Lizard"
    },
    {
      "type": "Bird",
      "qty": 3,
      "name": "Parrot"
    },
    {
      "type": "Mammal",
      "qty": 1,
      "name": "Elephant"
    },
    {
      "type": "Mammal",
      "qty": 10,
      "name": "Dog"
    }   
  ]
}

Transformer:

JavaScript
{
  "Result": "#grouparrayby($.Forest,type,all)"
}

Output:

JavaScript
{ 
   "Result":[ 
      { 
         "type":"Mammal",
         "all":[ 
            { 
               "qty":1,
               "name":"Hippo"
            },
            { 
               "qty":1,
               "name":"Elephant"
            },
            { 
               "qty":10,
               "name":"Dog"
            }
         ]
      },
      { 
         "type":"Bird",
         "all":[ 
            { 
               "qty":2,
               "name":"Sparrow"
            },
            { 
               "qty":3,
               "name":"Parrot"
            }
         ]
      },
      { 
         "type":"Amphibian",
         "all":[ 
            { 
               "qty":300,
               "name":"Lizard"
            }
         ]
      }
   ]
}

You can group using multiple "grouping elements". They should be seperated by a semicolon (:).

Consider the following input:

JavaScript
{
  "Vehicle": [
    {
      "type": "air",
      "company": "Boeing",
      "name": "airplane"
    },
    {
      "type": "air",
      "company": "Concorde",
      "name": "airplane"
    },
    {
      "type": "air",
      "company": "Boeing",
      "name": "Chopper"
    },
    {
      "type": "land",
      "company": "GM",
      "name": "car"
    },
    {
      "type": "sea",
      "company": "Viking",
      "name": "ship"
    },
    {
      "type": "land",
      "company": "GM",
      "name": "truck"
    }
  ] 
}

Transformer:

JavaScript
{
  "Result": "#grouparrayby($.Vehicle,type:company,all)"
}

Output:

JavaScript
{ 
   "Result":[ 
      { 
         "type":"air",
         "company":"Boeing",
         "all":[ 
            { 
               "name":"airplane"
            },
            { 
               "name":"Chopper"
            }
         ]
      },
      { 
         "type":"air",
         "company":"Concorde",
         "all":[ 
            { 
               "name":"airplane"
            }
         ]
      },
      { 
         "type":"land",
         "company":"GM",
         "all":[ 
            { 
               "name":"car"
            },
            { 
               "name":"truck"
            }
         ]
      },
      { 
         "type":"sea",
         "company":"Viking",
         "all":[ 
            { 
               "name":"ship"
            }
         ]
      }
   ]
}

Calling Custom Functions

You can make your own custom functions in C# and call them from your transformer JSON.
A custom function has to reside inside a public class and has to be a public static method.

A custom function is called using the following syntax:

JavaScript
#customfunction(dll name, FQN for the static function, argument1.......,argumentN)

Consider the following input:

JavaScript
{
  "tree": {
    "branch": {
      "leaf": "green",
      "flower": "red",
      "bird": "crow"
    }
  }
}

Transformer:

JavaScript
{
  "Season": "#customfunction(JUST.NET.Test,JUST.NET.Test.Season.findseason,
             #valueof($.tree.branch.leaf),#valueof($.tree.branch.flower))"
}

Custom function:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace JUST.NET.Test
{
    public class Season
    {
        public static string findseason(string leafColour, string flowerColour)
        {
            if (leafColour == "green" && flowerColour == "red")
                return "summer";
            else
                return "winter";
        }
    }
}

Output:

JavaScript
{"Season":"summer"}

Nesting of Functions

You can easily nest functions to do complex transformations.

An example of such a transformation is demonstrated by the example below:

Consider the following input:

JavaScript
{
  "Name": "Kari",
  "Surname": "Nordmann",
  "MiddleName": "Inger",
  "ContactInformation": "Karl johans gate:Oslo:88880000" ,
  "PersonalInformation": "45:Married:Norwegian"
}

Transformer:

JavaScript
{
  "FullName": "#concat(#concat
  (#concat(#valueof($.Name), ),#concat(#valueof($.MiddleName), )),#valueof($.Surname))",
  "Contact Information": {
    "Street Name": "#substring(#valueof($.ContactInformation),
    0,#firstindexof(#valueof($.ContactInformation),:))",
    "City": "#substring(#valueof($.ContactInformation),#add(#firstindexof
    (#valueof($.ContactInformation),:),1),#subtract(#subtract
    (#lastindexof(#valueof($.ContactInformation),:),#firstindexof
    (#valueof($.ContactInformation),:)),1))",
    "PhoneNumber": "#substring
    (#valueof($.ContactInformation),#add(#lastindexof
    (#valueof($.ContactInformation),:),1),#subtract
    (#lastindexof(#valueof($.ContactInformation),),#lastindexof
    (#valueof($.ContactInformation),:)))"
  },
  "Personal Information": {
    "Age": "#substring(#valueof($.PersonalInformation),
    0,#firstindexof(#valueof($.PersonalInformation),:))",
    "Civil Status": "#substring(#valueof
    ($.PersonalInformation),#add(#firstindexof
    (#valueof($.PersonalInformation),:),1),#subtract(#subtract
    (#lastindexof(#valueof($.PersonalInformation),:),#firstindexof
    (#valueof($.PersonalInformation),:)),1))",
    "Ethnicity": "#substring(#valueof
    ($.PersonalInformation),#add(#lastindexof
    (#valueof($.PersonalInformation),:),1),#subtract(#lastindexof
    (#valueof($.PersonalInformation),),#lastindexof
    (#valueof($.PersonalInformation),:)))"
  }

Output:

JavaScript
{
   "FullName":"Kari Inger Nordmann",
   "Contact Information":{
     "Street Name":"Karl johans gate",
     "City":"Oslo",
     "PhoneNumber":"88880000"
    },
   "Personal Information":{
     "Age":"45",
     "Civil Status":"Married",
     "Ethnicity":"Norwegian"
    }
}

Multiple Argument & Constant Functions

The transformation in the above scenario looks quite complex. And it could get quite messy when the string becomes longer. Also, since comma(,) is a reserved keyword, it is not possible to concatenate a comma to a string.

Hence, the following 3 functions have been introduced:

  • xconcat(string1,string2......stringx) - Concatenates multiple strings
  • xadd(int1,int2......intx) - Adds multiples integers
  • constant_comma() - Returns comma(,)
  • constant_hash() - Returns hash(#)

Consider the following input:

JavaScript
{
  "Name": "Kari",
  "Surname": "Nordmann",
  "MiddleName": "Inger",
  "ContactInformation": "Karl johans gate:Oslo:88880000" ,
  "PersonalInformation": "45:Married:Norwegian"
}

Transformer:

JavaScript
{
  "FullName": "#xconcat(#valueof($.Name),
  #constant_comma(),#valueof($.MiddleName),
               #constant_comma(),#valueof($.Surname))",
  "AgeOfParents": 
  "#xadd(#valueof($.AgeOfMother),#valueof($.AgeOfFather))"
}

Output:

JavaScript
{"FullName":"Kari,Inger,Nordmann",
"AgeOfParents":"67"}

Check for Existence

The following two functions have been added to check for existence:

  • exists(path)
  • existsandnotempty(path)

Consider the following input:

JavaScript
{
   "BuyDate": "2017-04-10T11:36:39+03:00",
   "ExpireDate": ""
}

Transformer:

JavaScript
{
  "BuyDateString": "#ifcondition(#exists($.BuyDate),
  true,#concat(Buy Date : ,#valueof($.BuyDate)),NotExists)",
  "BuyDateString2": "#ifcondition(#existsandnotempty($.BuyDate),
  true,#concat(Buy Date : ,#valueof($.BuyDate)),EmptyOrNotExists)",
  "ExpireDateString": "#ifcondition(#exists($.ExpireDate),
  true,#concat(Expire Date : ,#valueof($.ExpireDate)),NotExists)",
  "ExpireDateString2": "#ifcondition(#existsandnotempty($.ExpireDate),
  true,#concat(Expire Date : ,#valueof($.ExpireDate)),EmptyOrNotExists)",
  "SellDateString": "#ifcondition(#exists($.SellDate),
  true,#concat(Sell Date : ,#valueof($.SellDate)),NotExists)",
  "SellDateString2": "#ifcondition(#existsandnotempty($.SellDate),
  true,#concat(Sell Date : ,#valueof($.SellDate)),EmptyOrNotExists)"
}

Output:

JavaScript
{

   "BuyDateString":"Buy Date : 2017-04-10T11:36:39+03:00",
   "BuyDateString2":"Buy Date : 2017-04-10T11:36:39+03:00",
   "ExpireDateString":"Expire Date : ",
   "ExpireDateString2":"EmptyOrNotExists",
   "SellDateString":"NotExists",
   "SellDateString2":"EmptyOrNotExists"
}

Conditional Transformation

Conditional transformation can be achieved using the ifgroup function.

The function takes an expression as argument which should evaluate to a boolean value.

Consider the following input:

JavaScript
{
  "Tree": {   
    "Branch": "leaf",
    "Flower": "Rose"
  }
}

Transformer:

JavaScript
{
  "Result": {
    "Header": "JsonTransform",
    "#ifgroup(#exists($.Tree.Branch))": {
      "State": {
        "Value1": "#valueof($.Tree.Branch)",
        "Value2": "#valueof($.Tree.Flower)"
      }
    }
 }
}

Output:

JavaScript
{ 
   "Result":{ 
      "Header":"JsonTransform",
      "State":{ 
         "Value1":"leaf",
         "Value2":"Rose"
      }
   }
}

Now, for the same input, if we use the following transformer, we get a different output.

Transformer:

JavaScript
{
  "Result": {
    "Header": "JsonTransform",
    "#ifgroup(#exists($.Tree.Root))": {
      "State": {
        "Value1": "#valueof($.Tree.Branch)",
        "Value2": "#valueof($.Tree.Flower)"
      }
    }
 }
}

Output:

JavaScript
{ 
   "Result":{ 
      "Header":"JsonTransform"
   }
}

Dynamic Properties

We can now create dynamic properties using the eval function. The function takes an expression as an argument.

Consider the following input:

JavaScript
{
  "Tree": {   
    "Branch": "leaf",
    "Flower": "Rose"
  }
}

Transformer:

JavaScript
{
  "Result": {
      "#eval(#valueof($.Tree.Flower))": "x"
  }
}

Output:

JavaScript
{ 
   "Result":{ 
      "Rose":"x"
   }
} 

Schema Validation Against Multiple Schemas Using Prefixes

A new feature to validate a JSON against multiple schemas has been introduced in the new Nuget 2.0.XX. This is to enable namespace based validation using prefixes like in XSD.

Below is a sample code which you need to write to validate a JSON against 2 schemas using prefixes:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using JUST;
using System.IO;

namespace JUST.Test
{
    public class Program
    {
        public static void Main(string[] args)
        {
            string inputJson = File.ReadAllText("ValidationInput.json")//read input from JSON file.;
            string schemaJsonX = File.ReadAllText("SchemaX.json")//read first schema from JSON file.;
            string schemaJsonY = File.ReadAllText("SchemaY.json")//read second input from JSON file.;

            JsonValidator validator = new JsonValidator(inputJson)//create instance 
                                                        //of JsonValidator using the input.;
            validator.AddSchema("x", schemaJsonX)//Add first schema with prefix 'x'.;
            validator.AddSchema("y", schemaJsonY)//Add second schema with prefix 'y'.;

            validator.Validate();

        }
    }
}

In the above case if the validation is un-successful, an exception will be thrown with the validation errors.

Consider the validation input:

JavaScript
{
  "x.tree": { "x.branch": { "x.leaf": "1" } },
  "x.child": 1,
  "y.animal": 1
}

Schema X JSON:

JavaScript
{
  "properties": {
    "tree": {
      "type": "object",
      "properties": {
        "branch": {
          "type": "object",
          "properties": {
            "leaf": { "type": "string" }
          }
        }
      }
    },
    "child": { "type": "string" }
  }
}

Schema Y JSON:

JavaScript
{
  "properties": {
    "animal": { "type": "string" }
  }
}

The exception message thrown in the above case would be:

JavaScript
Unhandled Exception: System.Exception: Invalid type. Expected String but got Integer. 
Path '['x.child']', line 3, position 14. AND Invalid type. Expected String but got Integer. 
Path '['y.animal']', line 4, position 15.

Splitting JSON into Multiple JSON(s) Based Upon an Array Token

A JSON file containing an array can now be split into multiple JSON files, each representing a file for every array element.

Two new functions have been added for this purpose:

  • public static IEnumerable<string> SplitJson(string input,string arrayPath)
  • public static IEnumerable<JObject> SplitJson(JObject input, string arrayPath)

Consider the input:

JavaScript
{
  "cars": {
    "Ford": [
      {
        "model": "Taurus",
        "doors": 4
      },
      {
        "model": "Escort",
        "doors": 4
      },
      {
        "model": "Fiesta",
        "doors": 3
      },
      {
        "model": "Bronco",
        "doors": 5
      }
    ],
    "firstName": "John",
    "lastName": "Smith",
  }
}

Below is a sample code which splits the above input:

C#
string input = File.ReadAllText("Input.json");
List<string> outputs = JsonTransformer.SplitJson(input, "$.cars.Ford").ToList<string>();

The output will contain 4 JSON files:

JavaScript
{"cars":{"Ford":{"model":"Taurus",
"doors":4},"firstName":"John","lastName":"Smith"}}
JavaScript
{"cars":{"Ford":{"model":"Escort",
"doors":4},"firstName":"John","lastName":"Smith"}}
JavaScript
{"cars":{"Ford":{"model":"Fiesta",
"doors":3},"firstName":"John","lastName":"Smith"}}
JavaScript
{"cars":{"Ford":{"model":"Bronco",
"doors":5},"firstName":"John","lastName":"Smith"}}

 

Transforming JSON to Other Data Formats

JUST.NET can now transform JSON data into other generic formats too. All functions except the BULK FUNCTIONS are supported in this feature.

The #loop functions accepts an extra argument which defines the separator between the individual records.

#loop(path,seaperator).

If the separator is not defined, the default separator used is NEWLINE.

A new class called DataTransformer has been introduced for this new feature.

Example for JSON to XML

Sample code to transform from JSON to XML:

C#
string input = File.ReadAllText("Input.json");
string transformer = File.ReadAllText("DataTransformer.xml");
string transformedString = DataTransformer.Transform(transformer, input);

Input.json:

JavaScript
{
  "menu": {
    "id": {
      "file": "csv"
    },
    "value": {
      "Window": "popup"
    },
    "popup": {
      "menuitem": [
        {
          "value": "New",
          "onclick": {
            "action": "CreateNewDoc()"
          }
        },
        {
          "value": "Open",
          "onclick": "OpenDoc()"
        },
        {
          "value": "Close",
          "onclick": "CloseDoc()"
        }
      ]
    }
  },
  "x": [
    {
      "v": {
        "a": "a1,a2,a3",
        "b": "1",
        "c": "10"
      }
    },
    {
      "v": {
        "a": "b1,b2",
        "b": "2",
        "c": "20"
      }
    },
    {
      "v": {
        "a": "c1,c2,c3",
        "b": "3",
        "c": "30"
      }
    }
  ],
  "stringref": "thisisandveryunuasualandlongstring",
  "d": [ "one", "two", "three" ],
  "numbers": [ "1", "2", "3", "4", "5" ],
  "tree": {
    "branch": {
      "leaf": "green",
      "flower": "red",
      "bird": "crow"
    }
  },
  "Name": "Kari",
  "Surname": "Nordmann",
  "MiddleName": "Inger",
  "ContactInformation": "Karl johans gate:Oslo:88880000",
  "PersonalInformation": "45:Married:Norwegian",
  "AgeOfMother": "67",
  "AgeOfFather": "70",
  "BuyDate": "2017-04-10T11:36:39+03:00",
  "ExpireDate": "",
  "LogId": 5000510625
}

DataTransformer.xml:

JavaScript
<?xml version="1.0" encoding="UTF-8" ?>
<root>
  <root>
    <ifconditiontesttrue>#ifcondition(#valueof($.menu.id.file),csv,
               #valueof($.menu.value.Window),fail)</ifconditiontesttrue>
    <ifconditiontestfalse>#ifcondition(#valueof($.menu.id.file),xml,
               #valueof($.menu.value.Window),fail)</ifconditiontestfalse>
    <stringresult>
      <lastindexofand>#lastindexof(#valueof($.stringref),and)</lastindexofand>
      <firstindexofand>#firstindexof(#valueof($.stringref),and)</firstindexofand>
      <subsrting>#substring(#valueof($.stringref),8,10)</subsrting>
      <concat>#concat(#valueof($.menu.id.file),#valueof($.menu.value.Window))</concat>
    </stringresult>
    <mathresult>
      <add>#add(#valueof($.numbers[0]),3)</add>
      <subtract>#subtract(#valueof($.numbers[4]),#valueof($.numbers[0]))</subtract>
      <multiply>#multiply(2,#valueof($.numbers[2]))</multiply>
      <divide>#divide(9,3)</divide>
    </mathresult>
    <conacted>#concatall(#valueof($.d))</conacted>
    <sum>#sum(#valueof($.numbers))</sum>
    <avg>#average(#valueof($.numbers))</avg>
    <min>#min(#valueof($.numbers))</min>
    <max>#max(#valueof($.numbers))</max>
    <arrayconacted>#concatallatpath(#valueof($.x),$.v.a)</arrayconacted>
    <arraysum>#sumatpath(#valueof($.x),$.v.c)</arraysum>
    <arrayavg>#averageatpath(#valueof($.x),$.v.c)</arrayavg>
    <arraymin>#minatpath(#valueof($.x),$.v.b)</arraymin>
    <arraymax>#maxatpath(#valueof($.x),$.v.b)</arraymax>
  </root>
  <FullName>#concat(#concat(#concat(#valueof($.Name), ),
  #concat(#valueof($.MiddleName), )),#valueof($.Surname))</FullName>
  <Contact_Information>
    <City>#substring(#valueof($.ContactInformation),#add(#firstindexof
    (#valueof($.ContactInformation),:),1),
    #subtract(#subtract(#lastindexof(#valueof($.ContactInformation),:),
    #firstindexof(#valueof($.ContactInformation),:)),1))</City>
    <PhoneNumber>#substring(#valueof($.ContactInformation),
    #add(#lastindexof(#valueof($.ContactInformation),:),1),
    #subtract(#lastindexof(#valueof($.ContactInformation),),
    #lastindexof(#valueof($.ContactInformation),:)))</PhoneNumber>
    <Street_Name>#substring(#valueof($.ContactInformation),0,
    #firstindexof(#valueof($.ContactInformation),:))</Street_Name>
  </Contact_Information>
  <Personal_Information>
    <Age>#substring(#valueof($.PersonalInformation),0,
    #firstindexof(#valueof($.PersonalInformation),:))</Age>
    <Ethnicity>#substring(#valueof($.PersonalInformation),
    #add(#lastindexof(#valueof($.PersonalInformation),:),1),
    #subtract(#lastindexof(#valueof($.PersonalInformation),),
    #lastindexof(#valueof($.PersonalInformation),:)))</Ethnicity>
    <LogId>#valueof($.LogId)</LogId>
    <Civil_Status>#substring(#valueof($.PersonalInformation),
    #add(#firstindexof(#valueof($.PersonalInformation),:),1),
    #subtract(#subtract(#lastindexof(#valueof($.PersonalInformation),:),
    #firstindexof(#valueof($.PersonalInformation),:)),1))</Civil_Status>
  </Personal_Information>
  <Custom>#customfunction(JUST.NET.Test,JUST.NET.Test.Season.findseason,
  #valueof($.tree.branch.leaf),#valueof($.tree.branch.flower))</Custom>
  <iteration>
    "#loop($.numbers,<!--Record ends here-->)": {
    <Record>
      <CurrentValue>#currentvalue()</CurrentValue>
      <CurrentIndex>#currentindex()</CurrentIndex>
      <IsLast>#ifcondition(#currentindex(),#lastindex(),yes,no)</IsLast>
      <LastValue>#lastvalue()</LastValue>
      <SomeValue>#valueof($.LogId)</SomeValue>
    </Record>}
  </iteration> 
</root>

Output:

JavaScript
<?xml version="1.0" encoding="UTF-8" ?>
<root>
  <root>
    <ifconditiontesttrue>popup</ifconditiontesttrue>
    <ifconditiontestfalse>fail</ifconditiontestfalse>
    <stringresult>
      <lastindexofand>21</lastindexofand>
      <firstindexofand>6</firstindexofand>
      <subsrting>dveryunuas</subsrting>
      <concat>csvpopup</concat>
    </stringresult>
    <mathresult>
      <add>4</add>
      <subtract>4</subtract>
      <multiply>6</multiply>
      <divide>3</divide>
    </mathresult>
    <conacted>onetwothree</conacted>
    <sum>15</sum>
    <avg>3</avg>
    <min>1</min>
    <max>5</max>
    <arrayconacted>a1,a2,a3b1,b2c1,c2,c3</arrayconacted>
    <arraysum>60</arraysum>
    <arrayavg>20</arrayavg>
    <arraymin>1</arraymin>
    <arraymax>3</arraymax>
  </root>
  <FullName>Kari Inger Nordmann</FullName>
  <Contact_Information>
    <City>Oslo</City>
    <PhoneNumber>88880000</PhoneNumber>
    <Street_Name>Karl johans gate</Street_Name>
  </Contact_Information>
  <Personal_Information>
    <Age>45</Age>
    <Ethnicity>Norwegian</Ethnicity>
    <LogId>5000510625</LogId>
    <Civil_Status>Married</Civil_Status>
  </Personal_Information>
  <Custom>summer</Custom>
  <iteration>
    <Record>
      <CurrentValue>1</CurrentValue>
      <CurrentIndex>0</CurrentIndex>
      <IsLast>no</IsLast>
      <LastValue>5</LastValue>
      <SomeValue>5000510625</SomeValue>
    </Record><!--Record ends here-->
    <Record>
      <CurrentValue>2</CurrentValue>
      <CurrentIndex>1</CurrentIndex>
      <IsLast>no</IsLast>
      <LastValue>5</LastValue>
      <SomeValue>5000510625</SomeValue>
    </Record><!--Record ends here-->
    <Record>
      <CurrentValue>3</CurrentValue>
      <CurrentIndex>2</CurrentIndex>
      <IsLast>no</IsLast>
      <LastValue>5</LastValue>
      <SomeValue>5000510625</SomeValue>
    </Record><!--Record ends here-->
    <Record>
      <CurrentValue>4</CurrentValue>
      <CurrentIndex>3</CurrentIndex>
      <IsLast>no</IsLast>
      <LastValue>5</LastValue>
      <SomeValue>5000510625</SomeValue>
    </Record><!--Record ends here-->
    <Record>
      <CurrentValue>5</CurrentValue>
      <CurrentIndex>4</CurrentIndex>
      <IsLast>yes</IsLast>
      <LastValue>5</LastValue>
      <SomeValue>5000510625</SomeValue>
    </Record><!--Record ends here-->
  </iteration>
</root>

Example for JSON to CSV

Sample code to transform from JSON to CSV:

C#
string transformer = File.ReadAllText("Input.json");
string transformer = File.ReadAllText("DataTransformer.csv");
string transformedString = DataTransformer.Transform(transformer, input);

The input file is same as the XML example.

DataTransformer.csv:

JavaScript
"#loop($.numbers)": {#currentvalue(),#currentindex(),
#ifcondition(#currentindex(),#lastindex(),yes,no),#lastvalue(),#valueof($.LogId)}

Output:

JavaScript
1,0,no,5,5000510625
2,1,no,5,5000510625
3,2,no,5,5000510625
4,3,no,5,5000510625
5,4,yes,5,5000510625

Link to Test Source Code (Source Code Also Available Now)

I have made a GitHub repository which contains a C# project and has various transformation scenarios on an input JSON file.

I hope you will have fun using JUST for transforming your JSON documents.

History

  1. First version of JUST.NET
  2. Minor correction in the output of the first transformer (valueof)
  3. Math function DEVIDE has been changed to DIVIDE after feedback
  4. Added link to GitHub repository for test project
  5. Added "Array looping" section
  6. Added "Calling custom functions" section
  7. Added "Nesting of functions" section
  8. Added "Multiple argument & constant functions" section
  9. Added "Schema Validation against multiple schemas using prefixes" section
  10. Added "Check for existence" section
  11. Added information about the dotnetcore nuget package
  12. Added "Transforming JSON to other data formats" section
  13. Added "Splitting JSON into multiple JSON(s) based upon an array token" section
  14. Added "Nested array looping (looping within context)" functionality
  15. Added "Array grouping" section
  16. Added "Conditional transformation" & "Dynamic properties" sections
  17. Added "Operators" section, information about source code and .NET standard package

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Norway Norway
I have 14 years of IT experience on the .NET Stack. I am back-end\middleware expert focussing on databases, APIs and integration systems. I have been focussing a lot on AWS in the last 2 years.
https://github.com/WorkMaze

Comments and Discussions

 
GeneralRe: #ifcondition errors out when evaluating boolean true or false node Pin
pgpablo15-Aug-17 9:40
pgpablo15-Aug-17 9:40 
GeneralRe: #ifcondition errors out when evaluating boolean true or false node Pin
JUST_Neeraj16-Aug-17 6:14
professionalJUST_Neeraj16-Aug-17 6:14 
Question#delete for non-existence nodes Pin
Goluguri Vishnu Kiran Reddy24-Jul-17 6:06
professionalGoluguri Vishnu Kiran Reddy24-Jul-17 6:06 
AnswerRe: #delete for non-existence nodes Pin
JUST_Neeraj24-Jul-17 20:40
professionalJUST_Neeraj24-Jul-17 20:40 
SuggestionRe: #delete for non-existence nodes Pin
Goluguri Vishnu Kiran Reddy16-Aug-17 23:41
professionalGoluguri Vishnu Kiran Reddy16-Aug-17 23:41 
GeneralRe: #delete for non-existence nodes Pin
JUST_Neeraj17-Aug-17 0:08
professionalJUST_Neeraj17-Aug-17 0:08 
GeneralRe: #delete for non-existence nodes Pin
Goluguri Vishnu Kiran Reddy29-Aug-17 2:21
professionalGoluguri Vishnu Kiran Reddy29-Aug-17 2:21 
GeneralRe: #delete for non-existence nodes Pin
JUST_Neeraj29-Aug-17 3:54
professionalJUST_Neeraj29-Aug-17 3:54 
Hi, I assume that $.tree wasn't present in your input and that is why you were getting an exception. I have uploaded a new version of the nuget package. Now, if the node is not present it will just ignore it and not throw an exception. Hope this helps, regards!
GeneralRe: #delete for non-existence nodes Pin
Hyland Computer Systems15-Sep-17 7:15
Hyland Computer Systems15-Sep-17 7:15 
GeneralRe: #delete for non-existence nodes Pin
JUST_Neeraj15-Sep-17 7:46
professionalJUST_Neeraj15-Sep-17 7:46 
GeneralMy vote of 5 Pin
E. Scott McFadden21-Jun-17 9:12
professionalE. Scott McFadden21-Jun-17 9:12 
GeneralRe: My vote of 5 Pin
JUST_Neeraj21-Jun-17 10:02
professionalJUST_Neeraj21-Jun-17 10:02 
QuestionAnd same failure as XSLT? Pin
Thornik12-Jun-17 13:53
Thornik12-Jun-17 13:53 
AnswerRe: And same failure as XSLT? Pin
JUST_Neeraj13-Jun-17 8:35
professionalJUST_Neeraj13-Jun-17 8:35 
GeneralRe: And same failure as XSLT? Pin
Thornik14-Jun-17 3:36
Thornik14-Jun-17 3:36 
PraiseInteresting Pin
Leigh Pointer18-May-17 7:21
professionalLeigh Pointer18-May-17 7:21 
GeneralJSON Document Transforms (JDT) PinPopular
Luke Kolodziej16-May-17 10:58
professionalLuke Kolodziej16-May-17 10:58 
GeneralRe: JSON Document Transforms (JDT) Pin
martinrj3031-May-17 15:50
martinrj3031-May-17 15:50 
GeneralRe: JSON Document Transforms (JDT) Pin
martinrj3031-May-17 15:57
martinrj3031-May-17 15:57 
QuestionTransform to language other than JSON? Pin
pdprog16-May-17 9:37
pdprog16-May-17 9:37 
AnswerRe: Transform to language other than JSON? Pin
JUST_Neeraj16-May-17 10:09
professionalJUST_Neeraj16-May-17 10:09 
AnswerRe: Transform to language other than JSON? Pin
JUST_Neeraj3-Jul-17 1:04
professionalJUST_Neeraj3-Jul-17 1:04 
Questionproof that JSON transcends JS Pin
cpGlenn16-May-17 7:10
cpGlenn16-May-17 7:10 
AnswerRe: proof that JSON transcends JS Pin
JUST_Neeraj16-May-17 10:14
professionalJUST_Neeraj16-May-17 10:14 
QuestionDevide Pin
PalleDue15-May-17 1:38
PalleDue15-May-17 1:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.