JSONCrack Codebase Analysis - Part 4.2.1.1 - JsonEditor - debouncedUpdateJson

·

6 min read

jsoncrack is a popular opensource tool used to visualise json into a mindmap. It is built using Next.js.

We, at TThroo, love open source and perform codebase analysis on popular repositories, document, and provide a detailed explanation of the codebase. This enables OSS enthusiasts to learn and contribute to open source projects, and we also apply these learnings in our projects.

Part 4.2.1 talks in detail about JsonEditor and MonacoEditor and we also look into understanding the MonacoEditor.

In this article, we will get a better understanding of debouncedUpdateJson(json)

A recap:

setContents: async ({ contents, hasChanges = true, skipUpdate = false }) => {
    try {
      set({ ...(contents && { contents }), error: null, hasChanges });

      const isFetchURL = window.location.href.includes("?");
      const json = await contentToJson(get().contents, get().format);

      if (!useConfig.getState().liveTransformEnabled && skipUpdate) return;

      if (get().hasChanges && contents && contents.length < 80_000 && !isIframe() && !isFetchURL) {
        sessionStorage.setItem("content", contents);
        sessionStorage.setItem("format", get().format);
        set({ hasChanges: true });
      }

      debouncedUpdateJson(json);
    } catch (error: any) {
      if (error?.mark?.snippet) return set({ error: error.mark.snippet });
      if (error?.message) set({ error: error.message });
      useJson.setState({ loading: false });
      useGraph.setState({ loading: false });
    }
  },

We went into great details about the above code snippet in Part 4.2.1 except for debouncedUpdateJson(json);.

What is inside the debouncedUpdateJson?

const debouncedUpdateJson = debounce((value: unknown) => {
  useGraph.getState().setLoading(true);
  useJson.getState().setJson(JSON.stringify(value, null, 2));
}, 800);

Just two calls to zustand stores, named as useGraph and useJson.

useJson.getState().setJson(JSON.stringify(value, null, 2));

setJson has a side effect, it updates graph

setJson: json => {
    set({ json, loading: false });
    useGraph.getState().setGraph(json);
  },

This article strictly goes into only details of the debouncedUpdateJson and the side effects involved in it. This article does not provide a complete overview of the zustand stores yet, because we do not see a need yet. When trying to understand a new codebase, set a well defined scope.

But you might be wondering, what is a graph?

The following screenshot helps you understand what we are referring to and talking about.

Graph view mode

Graph is a view mode.

But why to have 2 stores, one for json and one for graph? the answer is, separation of concern. Json is used in monaco editor to load the json code and graph is used to render the mindmap or visualisation. Beginners would use one store to deal with json and graph in a single file. Don't do it. Separate your concerns through variables names, files names, folder organisation. It helps you in the long run.

useGraph

useGraph has a lot going on, but we will look for setGraph as this is the side effect performed in setJson explained above.

useGraph.getState().setGraph(json);

setGraph has the following code:

setGraph: (data, options) => {
    const { nodes, edges } = parser(data ?? useJson.getState().json);

    if (get().collapseAll) {
      set({ nodes, edges, ...options });
      get().collapseGraph();
    } else {
      set({
        nodes,
        edges,
        collapsedParents: [],
        collapsedNodes: [],
        collapsedEdges: [],
        graphCollapsed: false,
        ...options,
      });
    }
  },

You go step by step, exploring your unknowns.

parser

parser is imported on top of the file from src/lib/utils/json/jsonParser we saw a similar strategy for helpers that was using jsonAdapter.

It is a good practice to put all your helpers in a file. Initially you might not see a need to put certains functions in a file, but as you add more code/features, you should make the best judgement about which functions can be moved into a file.

You will reduce number of lines in a file, you do not want to end up 3k+ lines of code in a single file, rookie mistake. I have seen it before. My thoughts on such a file "God Almighty!?"

export function parser(jsonStr: string): Graph {
  try {
    const states = initializeStates();
    const parsedJsonTree = parseTree(jsonStr);

    if (!parsedJsonTree) {
      throw new Error("Invalid document");
    }

    traverse({ states, objectToTraverse: parsedJsonTree });

    const { notHaveParent, graph } = states;

    if (notHaveParent.length > 1 && parsedJsonTree.type !== "array") {
      const emptyNode = { id: null, text: "", isEmpty: true, data: {} };
      const emptyId = addNodeToGraph({ graph, ...emptyNode });

      notHaveParent.forEach(childId => addEdgeToGraph(graph, emptyId, childId));
    }

    if (states.graph.nodes.length === 0) {
      if (parsedJsonTree.type === "array") {
        addNodeToGraph({ graph: states.graph, text: "[]" });
      } else {
        addNodeToGraph({ graph: states.graph, text: "{}" });
      }
    }

    states.graph.nodes = states.graph.nodes.map(node => ({
      ...node,
      path: getNodePath(states.graph.nodes, states.graph.edges, node.id),
    }));

    return states.graph;
  } catch (error) {
    console.error(error);
    return { nodes: [], edges: [] };
  }
}

Take some time to try to understand the code above. It makes sense to introduce Reaflow. Reaflow is used to show the nodes and edges and form a mindmap.

Unknowns in the above code are:

  1. parseTree - This comes from jsonc-parser

  2. Traverse: a core util that deals with creating nodes and edges.

We could go into details of this, but the point of these tutorials is to understand how open sourced software is built ,the best practices around file structure, tech stack used.

Looking at traverse file's code you will understand that it has a lot of cases handled, all to build nodes and edges. Again be comfortable with this. You know how the core is now put together.

If you were to contribute to jsoncrack, atleast you now where to look for, how the codebase is organised.

Any changes in core should be dealt with extra care. It is named core for a good reason.

So, in the end parser returns a graph with nodes and edges that are used in Reaflow to render a map.

Reaflow map

Conclusion

Setting a json has a side effect that deals with updating graph. This graph with nodes and edges is used to Reaflow to render a mindmap. We touched the core utils when we looked at traverse functionality. You do not see any comments in the traverse file, because code is self explanatory by using variable names and function names that fit into the context.

In part 4.3, we will look at LiveEditor and understand how these nodes and edges in graph are put together to laod a visualisation.

In the upcoming parts, we will also talk about Toolbar and bottombar. This is where we can understand how the payments and subscription is integrated into this product using lemonsqueezy

And that would be the end of jsoncrack codebase analysis.

The next codebase to analyse is cal.com. This repo is larger than jsoncrack. It is a monorepo with packages and lots of stuff going behind the scenes.

Our approach and the intention behind writing these analysis of open source codebase is not get into complete details and understand how the core works, but to understand the design patterns, file structure, tech stack used so you can take these learnings and apply them in your projects. Because every project deals with different business logic, it would not make sense to get into business logic but rather the structure around it, in essence, laying down the foundation.

Thank you for reading till the end. If you have any questions or need help with a project or looking for an IT partner for your business, feel free to reach out to us at ram@tthroo.com