using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Amazon.Lambda.Annotations.SourceGenerator.Writers
{
///
/// This contains the functionality to manipulate a JSON blob
///
public class JsonWriter : ITemplateWriter
{
private JObject _rootNode;
public JsonWriter()
{
_rootNode = new JObject();
}
///
/// Checks if the dot(.) seperated jsonPath exists in the json blob stored at the _rootNode
///
/// dot(.) seperated path. Example "Person.Name.FirstName"
/// true if the path exist, else false
/// Thrown if the jsonPath is invalid
public bool Exists(string jsonPath)
{
if (!IsValidPath(jsonPath))
{
throw new InvalidDataException($"'{jsonPath}' is not a valid '{nameof(jsonPath)}'");
}
JToken currentNode = _rootNode;
foreach (var property in jsonPath.Split('.'))
{
if (currentNode == null)
{
return false;
}
currentNode = currentNode[property];
}
return currentNode != null;
}
///
/// This method converts the supplied token it into a type and sets it at the dot(.) seperated jsonPath.
/// Any non-existing nodes in the jsonPath are created on the fly.
/// All non-terminal nodes in the jsonPath need to be of type .
///
/// dot(.) seperated path. Example "Person.Name.FirstName"
/// The object to set at the specified jsonPath
/// This does not play any role while setting a token for the JsonWriter
/// Thrown if the jsonPath is invalid
/// Thrown if the terminal property in the jsonPath is null/empty or if any non-terminal nodes in the jsonPath cannot be converted to
public void SetToken(string jsonPath, object token, TokenType tokenType = TokenType.Other)
{
if (!IsValidPath(jsonPath))
{
throw new InvalidDataException($"'{jsonPath}' is not a valid '{nameof(jsonPath)}'");
}
if (token == null)
{
return;
}
var pathList = jsonPath.Split('.');
var lastProperty = pathList.LastOrDefault();
if (string.IsNullOrEmpty((lastProperty)))
{
throw new InvalidOperationException($"Cannot set a token at '{jsonPath}' because the terminal property is null or empty");
}
var terminalToken = GetDeserializedToken(token);
var currentNode = _rootNode;
for (var i = 0; i < pathList.Length-1; i++)
{
if (currentNode == null)
{
throw new InvalidOperationException($"Cannot set a token at '{jsonPath}' because one of the nodes in the path is null");
}
var property = pathList[i];
if (!currentNode.ContainsKey(property))
{
currentNode[property] = new JObject();
}
currentNode = currentNode[property] as JObject;
if (currentNode == null)
{
throw new InvalidOperationException($"Cannot set a value at '{jsonPath}' because the token at {property} does not represent a {typeof(JObject)}");
}
}
currentNode[lastProperty] = terminalToken;
}
///
/// Gets the object stored at the dot(.) seperated jsonPath. If the path does not exist then return the defaultToken.
/// The defaultToken is only returned if it holds a non-null value.
///
/// dot(.) seperated path. Example "Person.Name.FirstName"
/// The object that is returned if jsonPath does not exist.
/// Thrown if the jsonPath does not exist and the defaultToken is null
public object GetToken(string jsonPath, object defaultToken = null)
{
if (!Exists(jsonPath))
{
if (defaultToken != null)
{
return defaultToken;
}
throw new InvalidOperationException($"'{jsonPath}' does not exist in the JSON model");
}
JToken currentNode = _rootNode;
foreach (var property in jsonPath.Split('.'))
{
currentNode = currentNode[property];
}
return currentNode;
}
///
/// Gets the object stored at the dot(.) seperated jsonPath. If the path does not exist then return the defaultToken.
/// The defaultToken is only returned if it holds a non-null value.
/// The object is deserialized into type T before being returned.
///
/// dot(.) seperated path. Example "Person.Name.FirstName"
/// The object that is returned if jsonPath does not exist in the JSON blob. It will be convert to type T before being returned.
/// Thrown if the jsonPath does not exist and the defaultToken is null
public T GetToken(string jsonPath, object defaultToken = null)
{
var token = GetToken(jsonPath, defaultToken);
if (token == null)
{
throw new InvalidOperationException($"'{jsonPath}' points to a null token");
}
return GetDeserializedToken(token);
}
///
/// Deletes the token found at the dot(.) separated jsonPath. It does not do anything if the jsonPath does not exist.
///
/// dot(.) seperated path. Example "Person.Name.FirstName"
/// Thrown if the terminal property in jsonPath is null or empty
public void RemoveToken(string jsonPath)
{
if (!Exists(jsonPath))
{
return;
}
var pathList = jsonPath.Split('.');
var lastProperty = pathList.LastOrDefault();
if (string.IsNullOrEmpty(lastProperty))
{
throw new InvalidOperationException(
$"Cannot remove the token at '{jsonPath}' because the terminal property is null or empty");
}
var currentNode = _rootNode;
for (var i = 0; i < pathList.Length-1; i++)
{
var property = pathList[i];
currentNode = currentNode[property] as JObject;
}
currentNode.Remove(lastProperty);
}
///
/// Returns the template as a string
///
public string GetContent()
{
return JsonConvert.SerializeObject(_rootNode, formatting: Formatting.Indented);
}
///
/// Converts the JSON string into a
///
///
public void Parse(string content)
{
_rootNode = string.IsNullOrEmpty(content) ? new JObject() : JObject.Parse(content);
}
///
/// If the string does not start with '@', return it as is.
/// If a string value starts with '@' then a reference node is created and returned.
///
public object GetValueOrRef(string value)
{
if (!value.StartsWith("@"))
return value;
var jsonNode = new JObject();
jsonNode["Ref"] = value.Substring(1);
return jsonNode;
}
///
/// Validates that the jsonPath is not null or comprises only of white spaces. Also ensures that it does not have consecutive dots(.)
///
///
/// true if the path is valid, else fail
private bool IsValidPath(string jsonPath)
{
if (string.IsNullOrWhiteSpace(jsonPath))
return false;
return !jsonPath.Split('.').Any(string.IsNullOrWhiteSpace);
}
private T GetDeserializedToken(object token)
{
if (token is T deserializedToken)
{
return deserializedToken;
}
return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(token));
}
public IList GetKeys(string path)
{
try
{
return GetToken>(path).Keys.ToList();
}
catch (Exception ex)
{
throw new InvalidOperationException($"Unable to retrieve keys for the specified JSON path '{path}'.", ex);
}
}
}
}