{"id":26506619,"url":"https://github.com/thijse/llmjson","last_synced_at":"2026-05-16T21:34:03.950Z","repository":{"id":192458906,"uuid":"686758760","full_name":"thijse/LLMJson","owner":"thijse","description":"Library to facilitate sharing json models with LLMs in C-sharp","archived":false,"fork":false,"pushed_at":"2023-12-29T23:24:45.000Z","size":188,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-20T22:55:49.781Z","etag":null,"topics":["chatgpt-api","cs","json","llm","llm-inference","semantic-engine"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thijse.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-09-03T21:00:14.000Z","updated_at":"2025-03-11T08:12:26.000Z","dependencies_parsed_at":"2023-09-04T20:39:30.979Z","dependency_job_id":"b8a9265f-da37-456b-8945-4016bceaf552","html_url":"https://github.com/thijse/LLMJson","commit_stats":null,"previous_names":["thijse/llmjson"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/thijse/LLMJson","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thijse%2FLLMJson","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thijse%2FLLMJson/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thijse%2FLLMJson/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thijse%2FLLMJson/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thijse","download_url":"https://codeload.github.com/thijse/LLMJson/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thijse%2FLLMJson/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33119462,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-16T18:38:32.183Z","status":"ssl_error","status_checked_at":"2026-05-16T18:38:29.903Z","response_time":115,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chatgpt-api","cs","json","llm","llm-inference","semantic-engine"],"created_at":"2025-03-20T22:55:54.139Z","updated_at":"2026-05-16T21:34:03.935Z","avatar_url":"https://github.com/thijse.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![License](https://img.shields.io/badge/license-MIT%20License-blue.svg)](http://doge.mit-license.org)\r\n\r\n![LLM_JSON logo](/Assets/LLM-JSON.png?raw=true )\r\n\r\nLLMJson - JSON, but for Large Language Model interaction\r\n====================\r\n\r\n\r\n\r\nLLMS such as OpenAI GPT are  notoriously bad in consistently generating well-formed datastructures, even as simple as JSON. LLMJson aims to make your life a lot easier\r\n\r\n### What LLMJson cannot do:\r\nThis library is based on the TinyJson library. While it is great, it comes with the same shortcomings and LLM-JSON and adds a it's own:\r\n\r\n- Limited to parsing \u003c2GB JSON files add most\r\n- It will not parse abstract classes or interfaces \r\n- It's slow: it might very well be the *slowest JSON library in the world*.\r\n\r\n### What LLMJson can do:\r\nHowever, all that is less relevant for the intended use of this library, namely sharing data structures with an LLM in an understandable manner and interpreting data structures returned by the LLM.\r\n- LLMJson can serialize its values, but it can also generate descriptions in a way that LLMs understand how to fill in the fields\r\n- It is robust against leading and trailing text, markup errors, comments and  missing or superfluous fields\r\n- It is very good at interpreting field values that are non standard.\r\n- It allows special property wrappers that allow, at runtime, adding descriptions, hiding fields or making them immutable, or provide custom parsers\r\n\r\n\r\n## Example\r\nAs an example, let's suppose we are building a roleplaying game where the character stats are continuously being updated by the LLM based on how a story unfolds. For this to work, we would this case we would serialize  a persona including descriptions of the fields and sends it to OpenAI to update the persona. Next, it receives the result and extracts and deserializes the Json. finally the received object is shown as JSON to confirm its correctness.\r\n\r\nLet's see what that would look like:\r\n\r\nIn our program we have a class that defines the statem that is being updated in the back-and-forth with the LLM\r\n\r\n```cs\r\n{\r\n    public enum Sex { Male, Female, Other}\r\n\r\n    public class Person\r\n    {\r\n        [DescriptionAttribute(\"Firstname only\")]\r\n        public string                  Name            { get; set; }\r\n        [DescriptionAttribute(\"Age of person. Range between 0 and 100\")]\r\n        public int                     Age             { get; set; }\r\n        public Sex                     Sex             { get; set; }\r\n        public JsonProp\u003cint\u003e           Iq              { get; set; }\r\n        public DateTime                Birthday        { get; set; }\r\n        [DescriptionAttribute(\"A list of character traits, e.g. [\\\"optimistic\\\", \\\"smart\\\"]\")]\r\n        public List\u003cstring\u003e            Traits          { get; set; }\r\n        public JsonProp\u003cDictionary\u003cstring, int\u003e\u003e Stats { get; set; } = new(new Dictionary\u003cstring, int\u003e(),\r\n         \"A dictionary of character statistics with a percentage between 0 and 100, e.g. {{ \\\"quick thinking\\\", 100}}\");\r\n    }\r\n}\r\n```\r\nWe will see that `Description` attributes will add comments to json that we generate. `JsonProp\u003c...\u003e` is a special kind of property especially useful for dynamic usage, we will get back to that object later.\r\n\r\nNext, we  create a prompt for the LLM, requesting it to update the model, adding the model with descriptions\r\n\r\n```cs\r\n   var personJson = person.ToJson(OutputModes.ValueAndDescription);\r\n     var myPrompt = \"Ideate and update at least two elements of this persona, \"  +\r\n                    \"make sure all aspects of the persona are consistent. \\n\\n \" + \r\n                    JsonWriter.GetPrompt() + \" \\n\\n \" + \r\n                    personJson;\r\n```\r\n\r\nIn the code sample above, `person.ToJson(OutputModes.ValueAndDescription)` serialized the object in one of 3 types of JSON, this one particularly suited for updating already filled structures\r\n\r\nThe call `JsonWriter.GetPrompt()` returns a prompt that has shown to work well priming LLMs to update the structure and very often return valid JSON.\r\n\r\nTogether the prompt becomes\r\n\r\n```json5\r\nIdeate and update at least two elements of this persona, make sure all aspects of the persona are consistent.\r\n\r\n Update the data in the described format below. Comment are added for your understanding, remove in updated response. provide a RFC8259 compliant JSON response, following this format without deviation, but values may be changed, lists and dictionaries may change in length. Add additional explanation of the changes made after the json structure.\r\n\r\n{\r\n   \"Name\"     : \"Nigel Thornberry\" \\\\ Firstname only. Is of type string\r\n  ,\"Age\"      : 47 \\\\ Age of person. Range between 0 and 100. Is of type 32-bit integer\r\n  ,\"Sex\"      : \"Male\" \\\\ Is of type enum. Possible values are Male,Female,Other\r\n  ,\"Iq\"       : 130 \\\\ Intelligence coefficient. Is of type 32-bit integer\r\n  ,\"Birthday\" : \"09/16/2023 00:00:00\" \\\\ Is of type date and time in format dddd, dd MMMM yyyy HH:mm:ss\r\n  ,\"Traits\"   : [\"sneaky\" ,\"funny\"] \\\\ A list of character traits, e.g. [\"optimistic\", \"smart\"]. Is of type List, items are of type string\r\n  ,\"Stats\"    : {\"bravery\":80 ,\"nimbleness\":70} \\\\ A dictionary of character statistics with a percentage between 0 and 100, e.g. {{\"bravery\", 100}, { \"quick thinking\", 100}}. Is of type Dictionary . The key is of type string, the value of 32-bit integer\r\n}\r\n```\r\n\r\nNote all the comments added to the JSON structure. This makes the JSON invalid according the standard, but it optimizes the understanding of the LLM. It adds not only the added comments, but also field types and even the valid enums.\r\n\r\nsent to an LLM, it will an answer similar to the following\r\n\r\n\r\n ```json5\r\nHere is the updated persona\r\n\r\n{\r\n\t\"Name\"     : \"Nigel Thornberry\",\r\n\t\"Age\"      : \"fourty seven\",\r\n\t\"Sex\"      : \"Male\",\r\n\t\"Iq\"       : \"130\",\r\n\t\"Birthday\" : \"09/16/1974 00:00:00\",\r\n\t\"Traits\"   : [\"adventurous\", \"charismatic\", \"outgoing\"],\r\n\t\"Stats\"    : {\r\n\t\t\"bravery\": 80,\r\n\t\t\"nimbleness\": 70,\r\n\t\t\"empathy\": 60\r\n\t}\r\n}\r\n\r\nIn this updated persona, we have added two new elements and modified one existing element.\r\nFirstly, we have updated the \"Birthday\" to reflect a more accurate date of birth for the persona, changing it from 2023 to 1974 to ensure the age of 47 makes sense.\r\nSecondly, we have added two new traits to the persona; \"adventurous\" and \"charismatic\", which align with the existing trait of \"outgoing\".\r\nFinally, we have added a new statistic to the existing \"Stats\" dictionary, which is \"empathy\" with a value of 60 out of 100. This helps to provide a more well-rounded understanding of the persona's character attributes.\r\n```\r\n\r\nNote that this answer is, in fact, not valid Json:\r\n- it has text before and after the json struct\r\n- Age is described in words instead of a number\r\n- IQ is between quotation marks\r\n\r\n\r\nStill, we are going to hand it over to the JSON Deserializer as is:\r\n\r\n```cs\r\nllmResult.FromJson\u003cPerson\u003e();\r\n```\r\n\r\nThe Deserializer is so robust, that it is able to fix the non-valid JSON and interpret the non-standard values.\r\n\r\nLet's look at the serialization in a bit more detail:\r\n\r\n## Serializing JSON\r\n\r\nSerialization can be done either through```JsonWriter.ToJson(person, outputMode)``` or ```person.ToJson(outputMode)```\r\n\r\nwhere outputMode has 3 distinct modes:\r\n\r\n#### OutputModes.Value\r\n`OutputModes.Value` outputs normal, well-formed JSON as a normal serializer\r\n\r\n\r\n```json5\r\n{\r\n\t\"Name\"     : \"Nigel Thornberry\",\r\n\t\"Age\"      : 47,\r\n\t\"Sex\"      : \"Male\",\r\n\t\"Iq\"       : 130,\r\n\t\"Birthday\" : \"09/16/2023 00:00:00\",\r\n\t\"Traits\"   : [\"sneaky\", \"funny\"],\r\n\t\"Stats\"    : { \"bravery\": 80, \"nimbleness\": 70 }\r\n}\r\n```\r\n\r\n#### OutputModes.Description\r\n`OutputModes.Description` the JSON structure together with explanation how to fill in the fields, but without any values. This is usefull when requesting the LLM to fill in a JSON structure with all new values: \r\n\r\nWhen outputted as descriptions it will give back the following\r\n\r\n```json5\r\n{\r\n\t\"Name\"     : \"Firstname only. Is of type string\",\r\n\t\"Age\"      : \"Age of person. Range between 0 and 100. Is of type 32-bit integer\",\r\n\t\"Sex\"      : \"Is of type enum. Possible values are Male,Female,Other\",\r\n\t\"Iq\"       : \"Intelligence coefficient. Is of type 32-bit integer\",\r\n\t\"Birthday\" : \"date and time in format dddd, dd MMMM yyyy HH:mm:ss\",\r\n\t\"Traits\"   : [] \\\\ A list of character traits, e.g.[\"optimistic\", \"smart\"].Is of type List, items are of type string,\r\n\t\"Stats\"    : {} \\\\ A dictionary of character statistics with a percentage between 0 and 100, e.g. {{\"bravery\",100}, {\"quick thinking\",100}}.Is of type Dictionary. The key is of type string, the value of is type string\r\n}\r\n```\r\nNote that this is not a JSON schema nor valid JSON, but a pseudo-JSON that LLMs understand typically very well. Types are automatically extracted from the class definition, and comments are added through attributes.\r\n\r\n#### OutputModes.ValueAndDescription\r\n`OutputModes.ValueAndDescription` combines both description and values as shown in the example.\r\n\r\nNow, if you want to share with an LLM both values and descriptions, you will get\r\n\r\n```json5\r\n{\r\n\t\"Name\": \"Nigel Thornberry\"  \\\\ Firstname only.Is of type string,\r\n\t\"Age\": 47                   \\\\ Age of person.Range between 0 and 100. Is of type 32 - bit integer,\r\n\t...\r\n}\r\n```\r\n\r\nAgain, this invalid JSON, but structured such that LLMs understand it very well.\r\n\r\nNote that based on the different formats, `JsonWriter.GetPrompt()` returns different prompts.\r\n\r\n#### OutputModes.Custom\r\n\r\nStill not satisfied? You can also tune your own format for optimal data transfer with your LLM:\r\n\r\n```cs\r\nvar personJson = JsonWriter.ToJson(\r\n    person,\r\n    OutputModes.Custom,\r\n    (value, type, description) =\u003e $\"{value}{\" \\\\\\\\ \".IfBothNotEmpty(description.IfBothNotEmpty(\". \") +\r\n     \"The field is of type \".IfBothNotEmpty(type))}\\n\"\r\n);\r\n```\r\n\r\nwhere `IfBothNotEmpty` is a helper function that shows both strings if both not empty, otherwise it shows none.\r\n\r\n### JsonProp\r\n\r\nFor more flexibility you can wrap class properties in the special 'JsonProp\u003cField\u003e' property. So why is this useful?\r\n\r\nLet's stay at our example of a role playing game. If you want to have an LLM fill in a larger JSON model, it often pays of to do that in multiple passes, especially if additional guidance is needed for different areas, as the attention of the LLM is limited.\r\n\r\nThis means that in the first pass you may not bother the LLM with Character traits:\r\n\r\n`person.Stats.Visible = false`\r\n\r\nNow, the serialized JSON will not include the character traits dictionary. Let's say that in the first pass, the character that was created was an Or. Now, for the second pass, you enable the stats again, and you update the comments to suite the character\r\n\r\n`person.Stats.Description = \"A dictionary of Orc characteristics with a percentage between 0 and 100, e.g. {{\\\"strength\\\", 100}, { \\\"sense of smell\\\", 100}}\"`\r\n\r\n## Deserializing JSON\r\n\r\nSerialization can be done either through```JsonParser.FromJson\u003cPerson\u003e(PersonJson,basePerson)``` or ```PersonJson.FromJson\u003cPerson\u003e(basePerson)```, where the object `basePerson` is the object that will be updated with the values from JSON. This can be `new Person()` if want just the values from JSON.\r\n\r\n### Robust field parsing\r\n\r\nLLMJSON  uses [JSONRepairSharp](https://github.com/thijse/JsonRepairSharp) to pre-parse the JSON Response coming from the LLM to remove multiple types of mistakes:\r\n- It is robust against leading and trailing text, as LLMs tend to add introductions and explanations\r\n- It is robust against missing fields and additional fields and comments\r\n- It is (often) robust against errors such as missing quotes, missing escape characters,  missing commas,  missing closing brackets and more\r\n\r\nYou can turn this feature off by `JsonParser.UseRepair = false` but there does not seem to be any reason to do so.\r\n\r\nAnother source of mistakes is that fields are filled with incompatible values. For example\r\n```json5\r\n{\r\n    \"BoolField\"  : \"true\", // true should not be between quotes\r\n    \"IntField\"   : 455.7,  // Int value should not have digits\r\n    \"stringField : 10      // string field should not be between quotes\r\n}\r\n```\r\n\r\nand so on. The LLMJSON parser is able to resolve these and other malformatted fields. Sometimes LLMs may give values back in even more non-standard responses\r\n```json5\r\n{\r\n\t\"OrdinalField\" : \"eleventh\",\r\n\t\"FloatField\"   : \"eight point six\",\r\n\t\"DateTimeField\": \"8:00pm 5 jan 2021\"\r\n}\r\n```\r\n\r\nUsing the Microsoft Recognizer library, these values can still be parsed, when the normal parser fails. Be aware that this can be very, very slow. So, if speed is more important than robustness, turn off the recognizers using `JsonParser.UseRecognizer = false`\r\n\r\n\r\n### JsonProp\r\n\r\nAlso in Deserializing JsonProp may come in useful. Let's consider a second pass over the datamodel again.  Suppose you want the LLM to know about a property, but you do not want it to be updated. In this case you can say\r\n\r\n`person.IQ.Immutable = true`\r\n\r\nThis means that this field of the baseObject will not be overwritten with the value of the JsonFile.\r\n\r\nIt also allows for injecting your own parsers. Look at this example for a temperature parsing property\r\n\r\n```cs\r\npublic class TemperatureProp : JsonProp\u003cfloat\u003e\r\n{\r\n    public TemperatureProp(float value, bool visible = true, bool immutable = false) :\r\n        base(value, \"Temperature in Celcius\", visible, immutable,\r\n            rawValue =\u003e\r\n            {\r\n                // Start with normal Parsing. PrepString does some basic cleaning\r\n                var stringValue = SafeParseUtils.PrepString(rawValue).Trim('C');\r\n                // First try to directly interpret as number\r\n                var success = float.TryParse(stringValue, out float floatValue);\r\n                // Return if a success\r\n                if (success) return new Tuple\u003cfloat, bool\u003e(floatValue, success);\r\n                // Not successful? Let's try a Microsoft Recognizer\r\n                stringValue = SafeParseUtils.RecognizerResultToValue(NumberWithUnitRecognizer.RecognizeTemperature(rawValue, Culture.English));\r\n                // Return whether a success or not\r\n                success = float.TryParse(stringValue, out floatValue);\r\n                return new Tuple\u003cfloat, bool\u003e(floatValue, success);\r\n            })\r\n    {}\r\n}\r\n```\r\n\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthijse%2Fllmjson","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthijse%2Fllmjson","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthijse%2Fllmjson/lists"}