Thread overview
How to generate class/structure from JSON?
Sep 09, 2019
Suliman
Sep 09, 2019
JN
Sep 09, 2019
Adam D. Ruppe
September 09, 2019
I have complex JSON. I need to generate class/structure from it. Is there any tools for it?
September 09, 2019
On Monday, 9 September 2019 at 11:55:09 UTC, Suliman wrote:
> I have complex JSON. I need to generate class/structure from it. Is there any tools for it?

There are libraries to serialize/deserialize structs to/from JSON, but I am not aware of any tool that would generate the class structure from a JSON file.

I think you want this https://marketplace.visualstudio.com/items?itemName=quicktype.quicktype except it doesn't have support for D
September 09, 2019
On Monday, 9 September 2019 at 11:55:09 UTC, Suliman wrote:
> I have complex JSON. I need to generate class/structure from it. Is there any tools for it?

I have a program that kinda does it.

var a = json!q{ "foo": 12, "bar":"hi"}
toStaticType(a);

"struct {
        string bar;
        long foo;
}"


Here's a program that extracts that part from my other project into a single command line thing.
It depends on this file as an import: https://github.com/adamdruppe/arsd/blob/master/jsvar.d

---

import arsd.jsvar;

import std.stdio;
import std.file;

void main(string[] args) {
	var a = var.fromJson(readText(args[1]));
	auto context = new JsonAnalyzerContext();
	auto answer = structsFromJson(a, context).toString();
	writeln(answer);
}

class JsonAnalyzerContext {
	static class JsonType {
		JsonType commonType(JsonType type) {
			if(typeid(type) == typeid(this))
				return type; // same class == match
			if(type == this)
				return this; // structural equality also equals match
			return JsonVar.singleton;
		}

		override string toString() {
			return toBasicString(0);
		}

		final string indent(int indentationLevel, string s) {
			if(indentationLevel == 0)
				return s;

			string magic;
			foreach(i; 0 .. indentationLevel)
				magic ~= "\t";

			import std.array;
			return magic ~ s;// replace(s, "\n", "\n" ~ magic);
		}

		string toBasicString(int indentationLevel) {
			return indent(indentationLevel, "var");
		}
	}

	static class JsonArray : JsonType {
		this(JsonType of) {
			assert(of !is null);
			arrayOf = of;
		}

		JsonType arrayOf;
		override JsonType commonType(JsonType t) {
			if(auto array = cast(JsonArray) t) {
				return new JsonArray(arrayOf.commonType(array.arrayOf));
			} else if(auto n = cast(JsonNull) t) {
				return this;
			}
			return super.commonType(t);
		}

		override string toBasicString(int indentationLevel) {
			return arrayOf.toBasicString(indentationLevel) ~ "[]";
		}
	}

	static class JsonObject : JsonType {
		JsonType[string] members;

		JsonObject[string] memberStructs;

		bool nullable;

		override string toBasicString(int indentationLevel) {
			string code = indent(indentationLevel, (nullable ? "@nullable " : "") ~ "struct {\n");

			foreach(k, v; members) {
				code ~= v.toBasicString(indentationLevel + 1) ~ " " ~ k ~ ";\n";
			}

			code ~= indent(indentationLevel, "}");
			return code;
		}

		void addMember(string name, JsonType type) {
			if(auto ptr = name in members) {
				(*ptr) = ptr.commonType(type);
			} else
				members[name] = type;
		}

		override JsonType commonType(JsonType type) {
			if(cast(JsonNull) type) {
				this.nullable = true;
				return this;
			}
			return super.commonType(type);
		}
	}

	static class JsonInteger : JsonType {
		__gshared static typeof(this) singleton = new JsonInteger();

		override JsonType commonType(JsonType type) {
			if(auto dbl = cast(JsonFloat) type)
				return dbl; // double is the common type
			return super.commonType(type);
		}

		override string toBasicString(int indentationLevel) {
			return indent(indentationLevel, "long");
		}
	}
	static class JsonFloat : JsonType {
		__gshared static typeof(this) singleton = new JsonFloat();
		override JsonType commonType(JsonType type) {
			if(auto i = cast(JsonInteger) type)
				return this; // double is the most common type of long and double
			return super.commonType(type);
		}
		override string toBasicString(int indentationLevel) {
			return indent(indentationLevel, "double");
		}
	}
	static class JsonBoolean : JsonType {
		__gshared static typeof(this) singleton = new JsonBoolean();
		// true/false could implicitly convert to string or int, but
		// it really shouldn't. If that happens in the json, we'll just fall
		// back on null.

		override string toBasicString(int indentationLevel) {
			return indent(indentationLevel, "bool");
		}
	}
	static class JsonNull : JsonType {
		__gshared static typeof(this) singleton = new JsonNull();
		override JsonType commonType(JsonType type) {
			// strings and arrays can be null in D too, so we
			// return them if possible
			if(auto str = cast(JsonString) type)
				return str;
			if(auto arr = cast(JsonArray) type)
				return arr;

			// however, since json objects are represented as structs,
			// they cannot be null!
			if(auto obj = cast(JsonObject) type) {
				obj.nullable = true;
				return obj;
			}
			return super.commonType(type);
		}

		override string toBasicString(int indentationLevel) {
			return indent(indentationLevel, "typeof(null)");
		}
	}
	static class JsonString : JsonType {
		__gshared static typeof(this) singleton = new JsonString();
		override JsonType commonType(JsonType t) {
			if(auto n = cast(JsonNull) t) {
				return this;
			}
			return super.commonType(t);
		}

		override string toBasicString(int indentationLevel) {
			return indent(indentationLevel, "string");
		}
	}
	static class JsonFunction : JsonType {
		__gshared static typeof(this) singleton = new JsonFunction();
		// this should never happen!
		override string toBasicString(int indentationLevel) {
			return indent(indentationLevel, "typeof(null) /* function */");
		}
	}
	static class JsonVar : JsonType {
		__gshared static typeof(this) singleton = new JsonVar();
		// this is the supertype of everything
		override string toBasicString(int indentationLevel) {
			return indent(indentationLevel, "var");
		}
	}

	JsonObject findObject(string name) {
		if(auto it = name in objectTypes)
			return *it;
		auto i = new JsonAnalyzerContext.JsonObject();
		objectTypes[name] = i;
		return i;
	}

	JsonObject[string] objectTypes;
}

/// This analyzes a JSON input and tries to return matching static D types,
/// as a code string. It assumes that all members of an array have a common
/// type and all elements have the same members as the first one.
JsonAnalyzerContext.JsonType structsFromJson(var input, JsonAnalyzerContext context, JsonAnalyzerContext.JsonType suggestedType = null, string contextName = null) {
	final switch(input.payloadType()) {
		case var.Type.Object:
			if(input == null)
				return JsonAnalyzerContext.JsonNull.singleton;

			auto objSuggestion = cast(JsonAnalyzerContext.JsonObject) suggestedType;
			auto type = objSuggestion ? objSuggestion : context.findObject(contextName);

			foreach(k, v; input) {
				auto name = k.get!string;
				type.addMember(name, structsFromJson(v, context, null, contextName ~ "." ~ name));
			}

			return type;
		case var.Type.Array:
			JsonAnalyzerContext.JsonType common = null;
			foreach(v; input) {
				auto t = structsFromJson(v, context, common, contextName);
				if(common is null)
					common = t;
				else
					common = common.commonType(t);
			}
			if(common is null)
				common = JsonAnalyzerContext.JsonVar.singleton;
			return new JsonAnalyzerContext.JsonArray(common);
		case var.Type.String:
			return JsonAnalyzerContext.JsonString.singleton;
		case var.Type.Integral:
			return JsonAnalyzerContext.JsonInteger.singleton;
		case var.Type.Floating:
			return JsonAnalyzerContext.JsonFloat.singleton;
		case var.Type.Boolean:
			return JsonAnalyzerContext.JsonBoolean.singleton;
		case var.Type.Function:
			return JsonAnalyzerContext.JsonFunction.singleton;
	}
}


----


Note that you will have to edit the generated code a little. It doesn't generate 100% valid D out of the box, the names might be keywords and the structs it creates do not have names. For example:


struct {
        bool bool2;
        struct {
                long id;
                string name;
        }[] objarr;
        double float;
        typeof(null) null;
        string test;
        long int;
        struct {
                long[] longarray;
                string str;
                long subnumber;
        } object;
        bool bool1;
        long[] array;
        string rls;
}



That's one output and obviously that's not 100% valid D - the structs need names and child object syntax isn't quite right.

But it helps get you started reading a big json file into it.