Use .NET to convert JSON via AutoLISP

Use .NET to convert JSON via AutoLISP

CodeDing
Advisor Advisor
2,234 Views
9 Replies
Message 1 of 10

Use .NET to convert JSON via AutoLISP

CodeDing
Advisor
Advisor

Hello,

 

I understand it's a faux pas to ask for a product without perhaps some existing code, but I hope my example can serve you well enough as I am not savvy enough with .NET to get off in the right direction.

 

Since I know it's possible to create AutoLISP functions via .NET (Example HERE), I was wondering if perhaps anyone has or could provide a method to convert a JSON string to its equivalent list counterpart (an AutoLISP list). I am not concerned with a 100% parser, since I only want to convert JSON to a List, not vice-versa.

 

Here's an example of an input/output:

(setq str
  "{ \"prop1\":\"val1\",
     \"prop2\":123.4,
     \"prop3\":{
        [ \"a\":null,\"b\":true ],
        [ \"c\":1234,\"d\":false ]
     }
   }")
(JSON->List str)
...
JSON->List would return EITHER:
("prop1" "val1" "prop2" 123.4 "prop3" (("a" nil "b" t) ("c" 1234 "d" nil)))
..or..
("prop1" "val1" "prop2" 123.4 "prop3" (("a" NULL "b" t) ("c" 1234 "d" nil)))

 

I understand the general workflow in .NET. It could even be recursive, but I do not understand enough to manipulate a JSON string in .NET nor how to convert back to a LispDataType.

 

I am asking for a .dll to do this because my product is already 99% written in AutoLISP.

I have tried to create a JSON->List function in AutoLISP/VisualLISP but it's cumbersome,inefficient, and generally slow.

 

Any help / input / guidance / advice on the matter is appreciated.

Best,

~DD

0 Likes
Accepted solutions (1)
2,235 Views
9 Replies
Replies (9)
Message 2 of 10

_gile
Consultant
Consultant

Hi,

 

Why doing it with .NET if LISP is the goal?

(defun jason2list (str / strSubstAll)
  (defun strSubstAll (newStr pattern str / i)
    (setq i 0)
    (while (setq i (vl-string-search pattern str i))
      (setq str (vl-string-subst newStr pattern str i))
    )
  )
  (read
    (vl-string-translate
      "{}[]:,"
      "()()  "
      (strSubstAll
	"nil"
	"null"
	(strSubstAll
	  "nil"
	  "false"
	  (strSubstAll "T" "true" str)
	)
      )
    )
  )
)

 

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 3 of 10

CodeDing
Advisor
Advisor

@_gile ,

 

Thank you for the reply.

I did not mention, but I have been using this method and have reached its limitations which is why I am now asking for something better.

The (read ...) function actually has a maximum symbol length of 2305 characters. This can be demonstrated with the following function:

(defun c:TEST ( / x cnt)
  (setq x "" cnt 0)
  (while t
    (prompt (strcat "\n" (itoa (setq cnt (1+ cnt)))))
    (setq x (strcat x "x"))
    (read x)
  );while
);defun

You will see that when the 2306th character is added then an error occurs and it will no longer function.

While I understand that CONTINUOUS strings of 2305+ characters are not very common, they still occur and interrupt my program. Unless there is another efficient method you can think of that overcomes this 2305 character limit?

 

Best,

~DD

0 Likes
Message 4 of 10

_gile
Consultant
Consultant

Here's a way (probably not the most efficient).

 

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;

using System.Text.RegularExpressions;

namespace JasonToAutoLisp
{
    public class LispFunctions
    {
        /*
        (setq str
          "{ \"prop1\":\"val1\",
             \"prop2\":123.4,
             \"prop3\":{
                [ \"a\":null,\"b\":true ],
                [ \"c\":1234,\"d\":false ]
             }
           }")
        (JSON->List str)
        */

        [LispFunction("JSON->List")]
        public static ResultBuffer JasonToList(ResultBuffer resbuf)
        {
            if (resbuf == null)
                return null;
            var args = resbuf.AsArray();
            if (args.Length != 1)
                return null;
            if (args[0].TypeCode != (int)LispDataType.Text)
                return null;
            string arg = (string)args[0].Value;
            var result = new ResultBuffer();
            foreach (var str in Regex.Split(Regex.Replace(Regex.Replace(arg, @"\s+", ""), @"(\{|\[|]|})", ":$1:"), @":|,"))
            {
                switch (str)
                {
                    case string s when string.IsNullOrWhiteSpace(s):
                        break;
                    case string s when s == "{" || s == "[":
                        result.Add(new TypedValue((int)LispDataType.ListBegin));
                        break;
                    case string s when s == "}" || s == "]":
                        result.Add(new TypedValue((int)LispDataType.ListEnd));
                        break;
                    case string s when s == "true":
                        result.Add(new TypedValue((int)LispDataType.T_atom));
                        break;
                    case string s when s == "false" || s == "null":
                        result.Add(new TypedValue((int)LispDataType.Nil));
                        break;
                    case string s when int.TryParse(s, out int i):
                        result.Add(new TypedValue((int)LispDataType.Int32, i));
                        break;
                    case string s when double.TryParse(s, out double d):
                        result.Add(new TypedValue((int)LispDataType.Double, d));
                        break;
                    default:
                        result.Add(new TypedValue((int)LispDataType.Text, str.Trim('"')));
                        break;
                }
            }
            return result;
        }
    }
}

 

Attached the DLL to NETLOAD (do not forget to unblock the ZIP).



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 5 of 10

CodeDing
Advisor
Advisor

@_gile ,

 

Thank you for a solution to test. I get an error when trying to NETLOAD your solution.

I saved the .dll to my desktop and also have that path in my 'Trusted Locations'.

I do not currently have the ability to compile a solution myself. Any suggestions?

 

Command: NETLOAD
Cannot load assembly. Error details: System.IO.FileLoadException: Could not load file or assembly 'file:///C:\Users\.....\Desktop\JasonToAutoLisp.dll' or one of its dependencies. Operation is not supported. (Exception from HRESULT: 0x80131515)
File name: 'file:///C:\Users\.....\Desktop\JasonToAutoLisp.dll' ---> System.NotSupportedException: An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework. This release of the .NET Framework does not enable CAS policy by default, so this load may be dangerous. If this load is not intended to sandbox the assembly, please enable the loadFromRemoteSources switch. See http://go.microsoft.com/fwlink/?LinkId=155569 for more information.
   at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.InternalLoadFrom(String assemblyFile, Evidence securityEvidence, Byte[] hashValue, AssemblyHashAlgorithm hashAlgorithm, Boolean forIntrospection, Boolean suppressSecurityChecks, StackCrawlMark& stackMark)
   at System.Reflection.Assembly.LoadFrom(String assemblyFile)
   at Autodesk.AutoCAD.Runtime.ExtensionLoader.Load(String fileName)
   at loadmgd()

 

Best,

~DD

0 Likes
Message 6 of 10

_gile
Consultant
Consultant

you have to "Unblock" the ZIP or DLL (right click > Properties > General > Unblock).



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 7 of 10

CodeDing
Advisor
Advisor

@_gile ,

 

Thank you, I had never done that before. I got it to load and use.

There appears to be a problem with the regex that is being implemented.

My response has these characters within a string value in the JSON response "{" "}" "[" "]" ":".

It appears that your regex is creating lists of these values also. Is there a way to NOT implement the regex expression to text characters inside of a string?

 

I have attached an example of the JSON response I receive, so you can test with it also (if you're still up for the challenge). It is a Google Directions API response.

 

I understand my initial request can be a bit cumbersome. I hate being needy, I just don't have the current means to accomplish something like this myself. If you do not wish to help further, then I understand. You have provided me with a great deal of information with your initial code response and perhaps one day I can finish this work. 

 

Thanks again. Best,

~DD

0 Likes
Message 8 of 10

_gile
Consultant
Consultant
Accepted solution

I tried a new attempt using a Jason deserializer and recursion toconvert the generated dictionary into LISP lists.

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;

using System.Collections.Generic;
using System.Web.Script.Serialization;

namespace JasonToAutoLisp
{
    public class LispFunctions
    {
        [LispFunction("JSON->List")]
        public static ResultBuffer JasonToLisp(ResultBuffer resbuf)
        {
            if (resbuf == null)
                return null;
            var args = resbuf.AsArray();
            if (args.Length != 1)
                return null;
            if (args[0].TypeCode != (int)LispDataType.Text)
                return null;
            string arg = (string)args[0].Value;
            try
            {
                var dict = new JavaScriptSerializer().DeserializeObject(arg)
                    as Dictionary<string, dynamic>;
                var result = new ResultBuffer();
                FillBuffer(dict, result);
                return result;
            }
            catch (System.Exception ex)
            {
                Autodesk.AutoCAD.ApplicationServices.Core.Application.ShowAlertDialog(
                    $"Error: {ex.Message}");
                return null;
            }
        }

        private static void FillBuffer(Dictionary<string, dynamic> pairs, ResultBuffer result)
        {
            foreach (var pair in pairs)
            {
                result.Add(new TypedValue((int)LispDataType.Text, pair.Key));
                var obj = pair.Value;
                AddValue(result, pair.Value);
            }
        }

        private static void AddValue(ResultBuffer result, dynamic obj)
        {
            switch (obj)
            {
                case null:
                    result.Add(new TypedValue((int)LispDataType.Nil));
                    break;
                case string s:
                    result.Add(new TypedValue((int)LispDataType.Text, s));
                    break;
                case int i:
                    result.Add(new TypedValue((int)LispDataType.Int32, i));
                    break;
                case decimal d:
                    result.Add(new TypedValue((int)LispDataType.Double, d));
                    break;
                case bool b when !b:
                    result.Add(new TypedValue((int)LispDataType.Nil));
                    break;
                case bool b when b:
                    result.Add(new TypedValue((int)LispDataType.T_atom));
                    break;
                case Dictionary<string, object> d:
                    result.Add(new TypedValue((int)LispDataType.ListBegin));
                    FillBuffer(d, result);
                    result.Add(new TypedValue((int)LispDataType.ListEnd));
                    break;
                case object[] a:
                    result.Add(new TypedValue((int)LispDataType.ListBegin));
                    foreach (var o in a)
                        AddValue(result, o);
                    result.Add(new TypedValue((int)LispDataType.ListEnd));
                    break;
                default:
                    break;
            }
        }
    }
}

 

 

 

 

 

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 9 of 10

CodeDing
Advisor
Advisor

@_gile ,

 

I've done limited testing, but it's working great. Thank you VERY much!

 

Best,

~DD

0 Likes
Message 10 of 10

_gile
Consultant
Consultant

I thaught that returning an association list was closer to the Json object structure (key-value pairs), and the access to data can be done with the classical: (cdr (assoc key)).

 

The attached assembly defines two LISP functions json->list (same as previous) and json->alist.

(setq str
       "{ \"prop1\":\"val1\",
       \"prop2\":123.4,
       \"prop3\":[
         { \"a\":null,\"b\":true },
         { \"c\":1234,\"d\":false }
       ]
     }"
)

Results:

_$ (json->list str)
("prop1" "val1" "prop2" 123.4 "prop3" (("a" nil "b" T) ("c" 1234 "d" nil)))

_$ (json->alist str)
(("prop1" . "val1")
  ("prop2" . 123.4)
  ("prop3" (("a") ("b" . T)) (("c" . 1234) ("d")))
)

 

C# code for the json->alist function:

        [LispFunction("JSON->AList")]
        public static ResultBuffer JasonToAssocList(ResultBuffer resbuf)
        {
            if (resbuf == null)
                return null;
            var args = resbuf.AsArray();
            if (args.Length != 1)
                return null;
            if (args[0].TypeCode != (int)LispDataType.Text)
                return null;
            string arg = (string)args[0].Value;
            try
            {
                var dict = new JavaScriptSerializer().DeserializeObject(arg)
                    as Dictionary<string, dynamic>;
                var result = new ResultBuffer();
                AddDictionary(result, dict);
                return result;
            }
            catch (System.Exception ex)
            {
                Autodesk.AutoCAD.ApplicationServices.Core.Application.ShowAlertDialog(
                    $"Error: {ex.Message}");
                return null;
            }
        }

        private static void AddDictionary(ResultBuffer result, Dictionary<string, dynamic> dict)
        {
            foreach (var pair in dict)
            {
                result.Add(new TypedValue((int)LispDataType.ListBegin));
                result.Add(new TypedValue((int)LispDataType.Text, pair.Key));
                switch (pair.Value)
                {
                    case null:
                        result.Add(new TypedValue((int)LispDataType.ListEnd));
                        break;
                    case string s:
                        result.Add(new TypedValue((int)LispDataType.Text, s));
                        result.Add(new TypedValue((int)LispDataType.DottedPair));
                        break;
                    case int i:
                        result.Add(new TypedValue((int)LispDataType.Int32, i));
                        result.Add(new TypedValue((int)LispDataType.DottedPair));
                        break;
                    case decimal d:
                        result.Add(new TypedValue((int)LispDataType.Double, d));
                        result.Add(new TypedValue((int)LispDataType.DottedPair));
                        break;
                    case bool b when !b:
                        result.Add(new TypedValue((int)LispDataType.ListEnd));
                        break;
                    case bool b when b:
                        result.Add(new TypedValue((int)LispDataType.T_atom));
                        result.Add(new TypedValue((int)LispDataType.DottedPair));
                        break;
                    case Dictionary<string, object> d:
                        result.Add(new TypedValue((int)LispDataType.ListBegin));
                        AddDictionary(result, d);
                        result.Add(new TypedValue((int)LispDataType.ListEnd));
                        result.Add(new TypedValue((int)LispDataType.ListEnd));
                        break;
                    case object[] a:
                        foreach (var o in a) AddValue(result, o);
                        result.Add(new TypedValue((int)LispDataType.ListEnd));
                        break;
                    default:
                        break;
                }
            }
        }

        private static void AddValue(ResultBuffer result, object obj)
        {
            switch (obj)
            {
                case null:
                    result.Add(new TypedValue((int)LispDataType.Nil));
                    break;
                case string s:
                    result.Add(new TypedValue((int)LispDataType.Text, s));
                    break;
                case int i:
                    result.Add(new TypedValue((int)LispDataType.Int32, i));
                    break;
                case decimal d:
                    result.Add(new TypedValue((int)LispDataType.Double, d));
                    break;
                case bool b when !b:
                    result.Add(new TypedValue((int)LispDataType.Nil));
                    break;
                case bool b when b:
                    result.Add(new TypedValue((int)LispDataType.T_atom));
                    break;
                case Dictionary<string, object> d:
                    result.Add(new TypedValue((int)LispDataType.ListBegin));
                    AddDictionary(result, d);
                    result.Add(new TypedValue((int)LispDataType.ListEnd));
                    break;
                case object[] a:
                    result.Add(new TypedValue((int)LispDataType.ListBegin));
                    foreach (var o in a) AddValue(result, o);
                    result.Add(new TypedValue((int)LispDataType.ListEnd));
                    break;
                default:
                    break;
            }
        }

 

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub